﻿using IronPython.Hosting;
using LunarSF.SHomeWorkshop.LunarMarkdownEditor.Utils;
using LunarSF.SHomeWorkshop.LunarMarkdownEditor.Utils.NPOIHelp;
using MarkdownSharp;
using Microsoft.Scripting.Hosting;
using Microsoft.Win32;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;
using static IronPython.Modules._ast;

namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
    /// <summary>
    /// 本主要使用开源的 MarkdownSharp 库来对 Markdown 文本进行转换。
    /// 由于 MarkdownSharp 并未实现某些特定功能，LME 编写了这个类来对要处理的文本进行“预处理”、“追加处理”。
    /// 其原理是：MarkdownSharp 对 Html 会原样输出，不会处理。所以，LME 可以“抢先”将某些指定格式的文本先处理成 Html 文本，
    /// 也可以在 MarkdownSharp 处理后再对得到的 Html 进行追加处理（例如添加 ID 等）——这样就可以实现一些自定义的功能了。
    /// 事实上，不仅 LME 本身的特定功能是通过这个原理实现的，用户编写的自定义脚本也可以实现对文本的特殊处理，从而实现某些自定义功能。
    /// </summary>
    public class CustomMarkdownSupport
    {
        /// <summary>
        /// 在使用 MarkDown Sharp 转换后，再对所有h1进行处理，自动生成jQuery函数，这样就可以支持点击标题显示、隐藏文本了。
        /// </summary>
        /// <param name="htmlString">传入 Markdown Sharp 转换后的 Html 文本。</param>
        /// <returns></returns>
        public static string AppendCompile(string htmlString, out string footComments, string directoryMark = "./",
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollapse = MainWindow.HtmlHeadersCollapseType.No,
            bool fillBlankOn = true, bool compilePageMenu = false, string sourceFilePath = null,
            string previewPageLink = "", string nextPageLink = "", int choiceQuestionsCount = 0, bool forEpub = false, string testPaperId = "", int dbl_mark_index = 0)
        {
            if (string.IsNullOrEmpty(htmlString))
            {
                footComments = "";
                return "";
            }

            htmlString = FormatPreCodeBlock(htmlString);
            htmlString = FormatInlieCodeBlock(htmlString);
            htmlString = FormatMultiImageLinkInOneline(htmlString);

            htmlString = htmlString.Replace("\\_", "_").Replace("\\<em>", "_").Replace("\\</em>", "_");   // 下划线转义处理。

            //载入jquery库。这东西无论如何都应载入，且只应载入一次。
            StringBuilder javascriptBuilder = new StringBuilder();
            //jQueryStringBuilder.Append("<script src=\"" + directoryMark + "json2.js\"></script>");
            javascriptBuilder.Append(/*"<script src=\"" + directoryMark + "jquery-1.7.0.min.js\"></script>" +*/
                                     // 上面这行移动到 <head></head> 块中，防止因页面元素未载入而出现类似 “$未定义” 之类的错误。
                                     //"<script src=\"" + directoryMark + "jquery.jqprint-0.3.js\"></script>" +
                                     (compilePageMenu ? "<script src=\"" + directoryMark + "menu_light.js\"></script>" : "") +
                                     "<script language=\"javascript\">" +
                                         "function print() {" +
                                            "$(\"#printArea\").jqprint({" +
                                                    "importCSS: true" +
                                            "});" +
                                          "}" +
                                     "</script>");

            // HighLight.pack.js 依赖 jQuery.js
            //script += "<script src=\"" + directoryLevelMark + "Highlight~/highlight.pack.js\"></script>\n";
            //script += "<script>hljs.initHighlightingOnLoad();</script>\n";

            //即使是 utf-8 编码效果也不好，仍然会出错。
            //if (Globals.MainWindow.DefaultEncoding == "utf-8")
            //{
            //    //测试得知：HighLight.Pack.Js 只支持 UTF-8 编码！！！
            //    javascriptBuilder.Append("<script src=\"" + directoryMark + "Highlight~/highlight.pack.js\"></script>\n");
            //    javascriptBuilder.Append("<script>hljs.initHighlightingOnLoad();</script>\n");
            //}

            //用于设置片段预览区的高度，以尽可能利用空间。
            javascriptBuilder.Append("<script>function getHeight(){return document.body.scrollHeight;}</script>");
            javascriptBuilder.Append("<script>"  // 此方法用于在页面中复制选择题文本
                                        + "function copyChoiceQuestion(examDiv,questionTitleId,questionChoice0Id,questionChoice1Id,questionChoice2Id,questionChoice3Id)\n"
                                        + "{\n"
                                        + "var qTitle=document.getElementById(questionTitleId);\n"
                                        + "var answer = '';\n"
                                        + "var qChoiceText0=\"\",qChoiceText1=\"\",qChoiceText2=\"\",qChoiceText3=\"\";"
                                        + "var qChoice0=document.getElementById(questionChoice0Id);\n"
                                        + "if(qChoice0){ qChoiceText0=\"A.\" + qChoice0.innerText + \"\\r\\n\"; if(qChoice0.getAttribute('isAnswer')=='true'){answer += 'A';}}"
                                        + "var qChoice1=document.getElementById(questionChoice1Id);\n"
                                        + "if(qChoice1){ qChoiceText1=\"B.\" + qChoice1.innerText + \"\\r\\n\"; if(qChoice1.getAttribute('isAnswer')=='true'){answer += 'B';}}"
                                        + "var qChoice2=document.getElementById(questionChoice2Id);\n"
                                        + "if(qChoice2){ qChoiceText2=\"C.\" + qChoice2.innerText + \"\\r\\n\"; if(qChoice2.getAttribute('isAnswer')=='true'){answer += 'C';}}"
                                        + "var qChoice3=document.getElementById(questionChoice3Id);\n"
                                        + "if(qChoice3){ qChoiceText3=\"D.\" + qChoice3.innerText + \"\\r\\n\"; if(qChoice3.getAttribute('isAnswer')=='true'){answer += 'D';}}"
                                        + "var ta = document.createElement('textarea');\n"
                                        + "ta.value = qTitle.innerText + \"\\r\\n\" + qChoiceText0 + qChoiceText1 + qChoiceText2 + qChoiceText3 + \"【答案】\" + answer + \"\\r\\n\";\n"
                                        + "examDiv.appendChild(ta);\n"
                                        + "ta.select();\n"
                                        + "document.execCommand(\"Copy\");\n"
                                        + "ta.style.display = 'none';\n"
                                        + "alert('已将下列文本复制到剪贴板：\\r\\n==========\\r\\n' + ta.value);\n"
                                        + "}\n"
                                        + "function CopyAnswersLetter(){\n"
                                        + "var letters = '';\n"
                                        + "var lis = $(\"li[isChecked$='true']\");\n"
                                        + "if(lis.length < choiceExCount){\n"
                                        + "alert('请做完再复制答案字串！');\n"
                                        + "return;\n"
                                        + "}\n"
                                        + "for(var i = 0; i<lis.length;i++){\n"
                                        + "var li = lis[i];\n"
                                        + "var liMark = li.getAttribute('mark');\n"
                                        + "letters = letters + liMark;\n"
                                        + "}\n"
                                        + "var ta = document.createElement('textarea');\n"
                                        + "ta.value = letters.replace(/．/g,'');\n"
                                        + "var body = document.getElementsByTagName('body')[0];\n"
                                        + "body.appendChild(ta);\n"
                                        + "ta.select();\n"
                                        + "document.execCommand('Copy');\n"
                                        + "ta.style.display = 'none';\n"
                                        + "alert('已将下列文本复制到剪贴板：\\r\\n==========\\r\\n' + ta.value);\n"
                                        + "}\n"
                                        + "</script>");
            javascriptBuilder.Append("<script>"  // 此方法用于在页面中复制填空题文本
                            + "function copyFillBlankQuestion(examDiv,questionTitleId)\n"
                            + "{\n"
                            + "var qTitle=document.getElementById(questionTitleId);\n"
                            + "var ta = document.createElement('textarea');\n"
                            + "ta.value = qTitle.innerText + \"\\r\\n\";\n"
                            + "examDiv.appendChild(ta);\n"
                            + "ta.select();\n"
                            + "document.execCommand(\"Copy\");\n"
                            + "ta.style.display = 'none';\n"
                            + "alert('已复制');\n"
                            + "}\n"
                            + "</script>");
            javascriptBuilder.Append("<script>"
                            + "function getHtml()\n"
                            + "{\n"
                            + "var html = $('html').clone();\n"
                            + "var htmlString = html.html();\n"
                            + "return htmlString;\n"
                            + "}\n"
                            + "</script>");

            // 添加双标点标记的折叠/展开脚本
            for (int idbl = 1; idbl <= dbl_mark_index; idbl++)
            {
                javascriptBuilder.AppendLine($"<script>$(document).ready(function() {{$('#dbl_mark_header_{idbl}').click(function() {{$('#dbl_mark_para_{idbl}').toggle();}});}});</script>");
            }

            StringBuilder sbMenuText = new StringBuilder();

            if (fillBlankOn)
            {
                #region 添加填空模式的js代码

                var codeClickHandler = @"
    $(document).ready(
      function(){
        $('p>code').css('color', ""transparent"");
      }
    );
    $(document).ready(
      function(){
        $('li>code').css('color', ""transparent"");
      }
    );
    $(document).ready(
      function(){
        $('tr>code').css('color', ""transparent"");
      }
    );
    $(document).ready(
      function(){
        $('td>code').css('color', ""transparent"");
      }
    );
    $(document).ready(
      function(){
        $('.panel>code').css('color', ""transparent"");
      }
    );
    var pCodeClick = function() {
        $('p').live('click',
            function(){
              var mycss = document.getElementById('theme_link');
              if (mycss.getAttribute('href').indexOf('dark.css') > 0){
                $(this).find('code').css('color', 'yellow');
              }else{
                $(this).find('code').css('color', 'green');
              }
            }
          );
        }
    $(document).ready(pCodeClick);
    var liCodeClick = function(){
        $('li').live('click',
            function(){
              var mycss = document.getElementById('theme_link');
              if (mycss.getAttribute('href').indexOf('dark.css') > 0){
                $(this).find('code').css('color', 'yellow');
              }else{
                $(this).find('code').css('color', 'green');
              }
            }
          );
        }
    $(document).ready(liCodeClick);
    var trCodeClick = function(){
        $('tr').live('click',
            function(){
              var mycss = document.getElementById('theme_link');
                if (mycss.getAttribute('href').indexOf('dark.css') > 0){
                  $(this).find('code').css('color', 'yellow');
                }else{
                  $(this).find('code').css('color', 'green');
                }
              }
            );
          } 
    $(document).ready(trCodeClick);
    var panelCodeClick = function(){
        $('.panel').live('click',
            function(){
              var mycss = document.getElementById('theme_link');
              if (mycss.getAttribute('href').indexOf('dark.css') > 0){
                $(this).find('code').css('color', 'yellow');
              }else{
                $(this).find('code').css('color', 'green');
              }
            }
          );
        }
    $(document).ready(panelCodeClick);
";
                javascriptBuilder.Append("<script>");
                javascriptBuilder.Append(codeClickHandler);
                javascriptBuilder.Append("</script>");
                #endregion
            }

            StringBuilder sb = new StringBuilder();
            StringBuilder sbFootComment = new StringBuilder();

            var lineStrings = htmlString.Split(new char[] { '\r', '\n' });
            var isFootComment = false;
            var isHtmlElementTag = false;
            var isCustomStyleDiv = false;
            var isInnerBox = false;
            Stack<string> previewHtmlElementTags = new Stack<string>();
            var trimChars = new char[] { ' ', '　', '\t', };
            var idRegText = @"[ 　\t]*[\(（][ 　\t]*(\+?[@]|[@]\+?)[a-zA-Z]([a-zA-Z0-9\-_]|(</?em>))*?[ 　\t]*[\)）]";
            for (int index = 0; index < lineStrings.Length; index++)
            {
                #region 处理方块文本区域，不支持折叠
                Regex regexStartS = new Regex(@"^<p>\[[^\[\]]*</p>");

                Regex regSquareHeadWithId = new Regex(@"(?<=(^<p>[ 　]{0,3}\[[ 　\t]{0,}[\(（][ 　\t]{0,}([@]\+|\+[@])))[a-zA-Z]([a-zA-Z0-9\-_]|(</?em>))*?(?=([ 　\t]*?[\)）][ 　\t]*</p>$))");
                Match matchSquareHeadWithId = regSquareHeadWithId.Match(lineStrings[index]);
                if (matchSquareHeadWithId.Success)
                {
                    lineStrings[index] = "<p>[ div (+@" + matchSquareHeadWithId.Value.Trim() + ")</p>";
                }

                var matchStartS = regexStartS.Match(lineStrings[index]);
                var tail = lineStrings[index].Substring(lineStrings[index].IndexOf('[') + 1)  // 方块的标头文本、标尾文本正常情况下是会被忽略的
                    .ToLower().TrimStart(trimChars);
                if (matchStartS != null && matchStartS.Success)
                {
                    isFootComment = (tail.StartsWith("脚注") || tail.ToLower().StartsWith("foot comment"));
                    var previewHtmlElementTag = "";
                    //isHtmlElementTag = (tail.ToLower().StartsWith("div"));
                    // 不必担心转义问题，因为正常情况下，方块头、尾的文本都是被忽略的，属于不会呈现在 Html 中的“无意义文本”。
                    // 加冒号可以生成自定义 html tag，不加冒号直接写 div
                    var regHtmlTag = new Regex(@"(^[ 　\t]{0,}?[:：][ 　\t]{0,}[A-Za-z][A-Za-z0-9]{0,})|(^[ 　\t]{0,}?[dD][iI][vV](?=([ 　\t]{0,}?[(（])))");
                    var matchHtmlTag = regHtmlTag.Match(tail.Trim());
                    if (matchHtmlTag.Success)
                    {
                        isHtmlElementTag = true;
                        previewHtmlElementTag = matchHtmlTag.Value;
                        if (previewHtmlElementTag.StartsWith(":") || previewHtmlElementTag.StartsWith("："))
                        {
                            previewHtmlElementTag = previewHtmlElementTag.Substring(1).Trim(new char[] { ' ', '　', '\t', });
                            if (previewHtmlElementTag.Length <= 0)
                            {
                                isHtmlElementTag = false;
                            }
                            else
                            {
                                previewHtmlElementTags.Push(previewHtmlElementTag);
                            }
                        }
                        else
                        {
                            isHtmlElementTag = true;
                            previewHtmlElementTags.Push("div");
                        }
                    }
                    else
                    {
                        isHtmlElementTag = false;
                    }

                    // 上面这种写法仍然嫌繁琐，下面再提供一些“内置” div 标签——这样写起来更快。
                    // 所谓 innerbox 是指内置的一些带 css 选择器的 div。通常，它们应当包括： 
                    //   1. n  n取值范围是0-9，它们应当被自动转换为 div.ibox_n   内置样式，用户可自行编写 css 样式片段来定义其样式。
                    //   2. ?  应当被转换为 div.ibox_question                    内置样式，用户可自行编写 css 样式片段。
                    //   3. !  应当被转换为 div.ibox_exclamation                 内置样式，用户可自行编写 css 样式片段。
                    //   4. -  应当被转换为 div.ibox_minus                       内置样式，主要用于包围无序列表。
                    //   5. +  应当被转换为 div.ibox_plus                        内置样式，主要用于包围无序列表。
                    //   6. *  应当被转换为 div.ibox_star                        内置样式，主要用于包围无序列表。
                    //   7. ^  应当被转换为 div.ibox_caret                       内置样式，主要用于包围有序列表。——在 LME 中，^ 常用于排序或计数。
                    var regInnerBox = new Regex(@"^[ 　\t]{0,}?(\d|[\?？]|[!！]|[\-－]|[+＋]|[\*]|\^)[ 　\t]{0,}?(?=</p>$)");
                    var matchInnerBox = regInnerBox.Match(tail);
                    if (matchInnerBox.Success)
                    {
                        isInnerBox = true;
                        previewHtmlElementTag = "div";
                        previewHtmlElementTags.Push("div");
                    }

                    var regCustomStyleDiv = new Regex($@"^(({idRegText}|[ 　\t]*)?(\d|[\?？]|[!！]|[\-－]|[+＋]|[\*]|\^)?[ 　\t]*[\$￥])|((\d|[\?？]|[!！]|[\-－]|[+＋]|[\*]|\^)?({idRegText}|[ 　\t]*)?[\$￥])");
                    var matchCustomStyleDiv = regCustomStyleDiv.Match(tail);
                    if (matchCustomStyleDiv.Success)
                    {
                        isCustomStyleDiv = true;
                    }

                    if (isFootComment)
                    {
                        lineStrings[index] = "";  // 用来包围脚注的方块区域是无实体的。
                    }
                    else if (isHtmlElementTag)
                    {
                        var htmlElementId = "";
                        var htmlElementClass = "";
                        var regHtmlElementId = new Regex(@"(?<=([\(（]))(\+?[@]|[@]\+?)[a-zA-Z]([a-zA-Z0-9\-_]|(</?em>))*?(?=([\)）]))");  // 成对的 _ 不能直接用，会被 MarkdownSharp 转换为 <em> ！
                        var matchHtmlElementId = regHtmlElementId.Match(tail);
                        if (matchHtmlElementId.Success) htmlElementId = matchHtmlElementId.Value.Trim();
                        var isHideDiv = htmlElementId.StartsWith("+@") || htmlElementId.StartsWith("@+");
                        htmlElementId = htmlElementId.TrimStart(new char[] { '+', '@' });
                        var styleText = isHideDiv ? " style='display: none;'" : "";

                        var regHtmlElementClass = new Regex(@"(?<=([\(（][&]amp;))[a-zA-Z]([a-zA-Z0-9\-_]|(</?em>))*?(?=([\)）]))");  // 成对的 _ 不能直接用，会被 MarkdownSharp 转换为 <em> ！
                        var matchHtmlElementClass = regHtmlElementClass.Match(tail);
                        if (matchHtmlElementClass.Success) htmlElementClass = matchHtmlElementClass.Value.Trim();
                        htmlElementId = htmlElementId.Replace("<em>", "_").Replace("</em>", "_");
                        htmlElementClass = htmlElementClass.Replace("<em>", "_").Replace("</em>", "_");

                        lineStrings[index] = $"\n<{previewHtmlElementTag} id=\"{htmlElementId}\" class=\"{htmlElementClass}\"{styleText}>";
                    }
                    else if (isInnerBox)   // 上面要写成 [ (@xxx)  或者  [ (&xxx)  这样。还是比较麻烦。所以这里提供一些简单的写法。
                    {
                        // n     div.ibox_n
                        // ?     div.ibox_question
                        // !     div.ibox_exclamation
                        // -     div.ibox_minus
                        // +     div.ibox_plus
                        // *     div.ibox_star
                        // ^     div.ibox_caret
                        if (tail.EndsWith("</p>"))
                        {
                            tail = tail.Substring(0, tail.Length - 4);
                        }

                        var innerBoxMark = tail.Replace(" ", "")
                                               .Replace("　", "")
                                               .Replace("\t", "");
                        if (innerBoxMark.Length == 1)
                        {
                            if ("0123456789０１２３４５６７８９０".Contains(innerBoxMark))
                            {
                                lineStrings[index] = $"\n<div class=\"ibox_{FormatNumber(innerBoxMark)}\">";
                            }
                            else
                            {
                                switch (innerBoxMark)
                                {
                                    case "?":
                                    case "？":
                                        {
                                            lineStrings[index] = $"\n<div class=\"ibox_question\">";
                                            break;
                                        }
                                    case "!":
                                    case "！":
                                        {
                                            lineStrings[index] = $"\n<div class=\"ibox_exclamation\">";
                                            break;
                                        }
                                    case "-":
                                    case "－":
                                        {
                                            lineStrings[index] = $"\n<div class=\"ibox_minus\">";
                                            break;
                                        }
                                    case "+":
                                    case "＋":
                                        {
                                            lineStrings[index] = $"\n<div class=\"ibox_plus\">";
                                            break;
                                        }
                                    case "*":
                                        {
                                            lineStrings[index] = $"\n<div class=\"ibox_star\">";
                                            break;
                                        }
                                    case "^":
                                        {
                                            lineStrings[index] = $"\n<div class=\"ibox_caret\">";
                                            break;
                                        }
                                    default:
                                        {
                                            lineStrings[index] = $"\n<div class=\"ibox_unknown\">";
                                            break;
                                        }
                                }
                            }
                            isInnerBox = false;
                        }
                        else
                        {
                            lineStrings[index] = $"\n<div class=\"ibox_unknown\">";
                            isInnerBox = false;
                        }
                    }
                    else if (isCustomStyleDiv)    // 自带简单样式表的方块，不能加自定义class ，但可以加自定义 id
                    {
                        var htmlElementId = "";
                        var regHtmlElementId = new Regex(@"(?<=([\(（]))(\+?[@]|[@]\+?)[a-zA-Z]([a-zA-Z0-9\-_]|(</?em>))*?(?=([\)）]))");  // 成对的 _ 不能直接用，会被 MarkdownSharp 转换为 <em> ！
                        var matchHtmlElementId = regHtmlElementId.Match(tail);
                        if (matchHtmlElementId.Success) htmlElementId = matchHtmlElementId.Value.Trim();
                        var isHideDiv = (htmlElementId.StartsWith("+@") || htmlElementId.StartsWith("@+")) && forEpub == false;    // 导出 epub 电子书时，总是显示的（因为不支持脚本，无法这样做）。
                        htmlElementId = htmlElementId.TrimStart(new char[] { '+', '@' });
                        var idText = "";
                        if (string.IsNullOrWhiteSpace(htmlElementId) == false)
                        {
                            idText = " id='" + htmlElementId + "'";
                        }
                        var styleText = "";
                        var regSimpleStyle = new Regex(@"(?<=([ 　\t]{0,}?))(\d|[\?？]|[!！]|[\-－]|[+＋]|[\*]|\^)?[ 　\t]{0,}?[\$￥].*?(?=(</p>$))");
                        var matchSimpleStyle = regSimpleStyle.Match(tail);
                        if (matchSimpleStyle.Success)
                        {
                            styleText = ConvertToCssStyle(matchSimpleStyle, isHideDiv, out string mark);
                            var classMark = ConvertClassMark(mark);
                            lineStrings[index] = $"<div{idText}{classMark}{styleText}>\n";
                        }
                    }
                    else
                    {
                        var tableID = "";
                        var tableClass = "";
                        var regId = new Regex(@"(?<=([\(（][@]))[a-zA-Z]([a-zA-Z0-9\-_]|(</?em>))*?(?=([\)）]))");
                        var matchId = regId.Match(tail);
                        if (matchId.Success) tableID = matchId.Value.Trim().Replace("<em>", "_").Replace("</em>", "_"); ;
                        var regClass = new Regex(@"(?<=([\(（][&]amp;))[a-zA-Z]([a-zA-Z0-9\-_]|(</?em>))*?(?=([\)）]))");
                        var matchClass = regClass.Match(tail);
                        var title = GetTitleFromTail(tail);

                        if (string.IsNullOrWhiteSpace(title) == false)
                        {
                            title = $"<p class=\"lme_div_head_title\">{title}</p>\n";
                        }

                        if (matchClass.Success) tableClass = " " + matchClass.Value.Trim().Replace("<em>", "_").Replace("</em>", "_"); ;

                        lineStrings[index] = $"<table id=\"{tableID}\" border=\"0\" cellspacing=\"0\" class=\"square-block{tableClass}\">" +
                                             "<tbody border=\"0\">" +
                                             "<tr border=\"0\">" +
                                             "<td class=\"square-block-left\"/>" +
                                             "<td class=\"square-block-content\"><div class='square-block-content'>\n" +
                                             title;

                    }
                }
                else
                {
                    Regex regexEnd = new Regex(@"^<p>[ 　]{0,3}\].*</p>");
                    var matchEnd = regexEnd.Match(lineStrings[index]);
                    if (matchEnd != null && matchEnd.Success)
                    {
                        if (isFootComment)
                        {
                            lineStrings[index] = "";  // 用来包围脚注的方块区域是无实体的。
                            isFootComment = false;
                        }
                        else if (isHtmlElementTag || isInnerBox)
                        {
                            try
                            {
                                var previewHtmlElementTag = previewHtmlElementTags.Pop();
                                if (previewHtmlElementTag != null && previewHtmlElementTag.Length > 0)
                                {
                                    lineStrings[index] = $"</{previewHtmlElementTag}>";
                                }
                                else
                                {
                                    lineStrings[index] = "\n";
                                }

                                isHtmlElementTag = false;
                                isInnerBox = false;
                            }
                            catch
                            {
                                lineStrings[index] = "\n";   // 忽略找不到配对的 ]。
                            }
                        }
                        else if (isCustomStyleDiv)
                        {
                            lineStrings[index] = "</div>\n";
                            isCustomStyleDiv = false;
                        }
                        else
                        {
                            var tailTitle = GetTitleFromTail(tail);
                            if (string.IsNullOrWhiteSpace(tailTitle) == false)
                            {
                                tailTitle = $"<p class=\"lme_div_tail_title\">{tailTitle}</p>\n";
                            }

                            lineStrings[index] = $"{tailTitle}</div></td>" +
                                                 "<td class=\"square-block-right\"/>" +
                                                 "</tr>" +
                                                 "</tbody>" +
                                                 "</table>";
                        }
                    }
                }

                if (isFootComment)
                {
                    sbFootComment.Append(lineStrings[index]);
                    sbFootComment.Append("\n");
                }
                else  // if div or not
                {
                    // 无论是否 div 其中的内容行都保留原样。
                    sb.Append(lineStrings[index]);
                    sb.Append("\n");
                }
                #endregion
            }
            htmlString = sb.ToString();
            footComments = sbFootComment.ToString();

            #region 解决相对路径的URL转义，有Bug。

            //Regex aLinkHref = new Regex(@"(?<=(<[Aa] {1,}.*[hH][rR][eE][fF]=[""'])).*(?=[""'])");
            //var matchesOfHrefs = aLinkHref.Matches(htmlString);
            //for (int i = matchesOfHrefs.Count - 1; i >= 0; i--)
            //{
            //    htmlString = htmlString.Substring(0, matchesOfHrefs[i].Index) +
            //        UrlEncode(matchesOfHrefs[i].Value) +
            //        htmlString.Substring(matchesOfHrefs[i].Index + matchesOfHrefs[i].Length);
            //}

            #endregion

            #region 新版的H1~H6六级标题折叠的代码

            var addCollapseJavascriptCode = false;
            if (htmlHeadersCollapse == MainWindow.HtmlHeadersCollapseType.Auto ||
                htmlHeadersCollapse == MainWindow.HtmlHeadersCollapseType.Manual)
            {
                addCollapseJavascriptCode = true;
            }

            List<HtmlHeaderInfo> infos = new List<HtmlHeaderInfo>();
            var regexOfHeaders = new Regex(@"\<[hH][123456] ?.*\>.*\<\/[hH][123456]\>");

            htmlString = FormatHeaderTagss(htmlString);
            // 上面用以去除 <hx>xxx</hx> 中的换行符，防止正则匹配不到！

            var matches = regexOfHeaders.Matches(htmlString);
            foreach (Match match in matches)
            {
                if (match != null && match.Success && match.Value.ToLower().IndexOf(" class=\"") < 0)
                {
                    // 为什么要限制 class ？这是为了给用户更多的自由。
                    // 如果用户只需要一些 加粗的标题，而不需要它带折叠等功能，而且还希望自定义它的具体样式
                    // 就可以给自己写的 Html 标题指定 css class，并在自定义 css 文件中写好它的样式。
                    // 如果不喜欢 Html 语法，甚至还可以利用 Python 脚本自定义语法。

                    string header;
                    int level = GetHtmlHeaderLevel(match.Value, out header);
                    var info = new HtmlHeaderInfo()
                    {
                        Level = level,
                        Header = header,
                        Match = match,
                    };
                    infos.Add(info);
                }
            }

            for (int i = 0; i < infos.Count; i++)
            {
                var info = infos[i];
                for (int j = i - 1; j >= 0; j--)
                {
                    var preInfo = infos[j];
                    if (preInfo.IsClosed == false && info.Level <= preInfo.Level)
                    {
                        info.PreviewHeaderPanelCloseDivsMark += "</div>";
                        preInfo.IsClosed = true;
                    }
                }

                var panelIndex = i + 1;
                if (addCollapseJavascriptCode)
                {
                    info.JavaScriptText = "<script>$(document).ready(function(){"
                        + "$(\"#title" + panelIndex + "\").click(function(){"
                        + "$(\"#panel" + panelIndex + "\").toggle();"
                        + "if($(\"#panel" + panelIndex + "\").is(\":hidden\")){"
                        + "$(\"#header_span" + panelIndex + "\").html('◆ ');}else{"
                        + "$(\"#header_span" + panelIndex + "\").html('◇ ');}"
                        + "});});</script>";
                }
                else info.JavaScriptText = "";

                var autoHideText = "";
                if (Globals.MainWindow.HtmlHeadersCollapse == MainWindow.HtmlHeadersCollapseType.Auto)
                {
                    autoHideText = " style='display: none;'";
                }

                var headerClass = Globals.MainWindow.CleanHeaders ? "clean" : "title";
                //clean headers 仍然要支持折叠。

                var hideMarkText = "";
                if (addCollapseJavascriptCode)
                {
                    hideMarkText = (Globals.MainWindow.HtmlHeadersCollapse == MainWindow.HtmlHeadersCollapseType.Manual ? "◇" : "◆");
                }

                info.Html = $"<h{info.Level} class='{headerClass}' id='title{panelIndex}' title='点击展开/折叠'><span id='header_span{panelIndex}' class='header_span'>{hideMarkText} </span>{info.Header}</h{info.Level}>";
                var divStartMark = "";
                if (htmlHeadersCollapse != MainWindow.HtmlHeadersCollapseType.No)
                {
                    divStartMark = $"<div class='panel_title_h{info.Level}' id='panel{panelIndex}'{autoHideText}>";
                }
                info.NewText = info.PreviewHeaderPanelCloseDivsMark + info.Html + info.JavaScriptText + divStartMark;
            }

            var menuHtml = "";

            if (compilePageMenu && htmlHeadersCollapse != MainWindow.HtmlHeadersCollapseType.No)
            {
                for (int i = infos.Count - 1; i >= 0; i--)
                {
                    //<span id="" class="anchor"></span>
                    var gotoTopLinkText = "";
                    var info = infos[i];
                    if (i > 0 && compilePageMenu && info.Level == 1)
                    {
                        gotoTopLinkText = "<p class=\"back_to_top_link\"><a href=\"#__TOP_4E4ABC53-B143-46FF-93CF-F9381EAD8E14__\">回到顶部</a></p>";
                        //其实只需要使用 <a href="#">返回顶部</a> 就可以了，因为当 href 指向 # 时，表示指向网页自身。
                    }

                    var anchorIdText = Guid.NewGuid().ToString();
                    var anchorText = $"<span id=\"{anchorIdText}\" class=\"anchor\"></span>";
                    htmlString = htmlString.Substring(0, info.Match.Index) + gotoTopLinkText + anchorText + info.NewText + htmlString.Substring(info.Match.Index + info.Match.Length);
                    var prefixSpace = (info.Level == 1 ? "" : "　");
                    sbMenuText.Insert(0, $"<p>{prefixSpace}<a href=\"#{anchorIdText}\">{info.Header}</a></p>");
                    //{new string('　', Math.Max(0, info.Level - 1))}加上会有缩进效果，但右边栏宽度不够。
                }
            }
            else
            {
                for (int i = infos.Count - 1; i >= 0; i--)
                {
                    var info = infos[i];
                    htmlString = htmlString.Substring(0, info.Match.Index) + info.NewText + htmlString.Substring(info.Match.Index + info.Match.Length);
                }
            }

            for (int i = 0; i < infos.Count; i++)
            {
                if (infos[i].IsClosed == false && htmlHeadersCollapse != MainWindow.HtmlHeadersCollapseType.No)
                    htmlString += "</div>";
            }

            if (addCollapseJavascriptCode)
            {
                htmlString += "<script>$(document).ready(function() {"  //折叠全部标题。
                           + "$('#file_header').click(function() {"
                           + "$('.panel_title_h1,.panel_title_h2,.panel_title_h3,.panel_title_h4,.panel_title_h5,.panel_title_h6').hide();$('.header_span').html('◆ ');});"
                           + "});</script>"
                           + "<script>$(document).ready(function() {"
                           + "$('#file_header').dblclick(function() {"
                           + "$('.panel_title_h1,.panel_title_h2,.panel_title_h3,.panel_title_h4,.panel_title_h5,.panel_title_h6').show();$('.header_span').html('◇ ');});"
                           + "});</script>";
            }

            if (sbMenuText.Length > 0)
            {
                menuHtml = "<div id=\"left_menu\">"
                             + "<div class=\"left_menu_content\">"
                             + sbMenuText.ToString()
                             + "<p><a href=\"#__TOP_4E4ABC53-B143-46FF-93CF-F9381EAD8E14__\">回到顶部</a></p>"
                             //下面这行是编译整个工作区时才应该添加的“回到目录页”链接
                             + (string.IsNullOrWhiteSpace(sourceFilePath) == false ? ("<hr/><p>" + previewPageLink + " " + BuildIndexOrHomePageLink(sourceFilePath, out string indexPath) + " " + nextPageLink + "</p>") : "")
                             + "</div>"
                             + "<strong class=\"left_menu_title\"><span> </span></strong>"
                             + "</div>";
            }

            #endregion

            // 在这里处理易出现混乱，危险
            //#region 处理行合并（以 “^ ”开头的行合并到上一行）
            //var regMergeParagraphs = new Regex(@"</[pP]>[ \t\r\n]{0,}<[pP]>\^[ 　\t]{1,}", RegexOptions.Multiline );
            //htmlString = regMergeParagraphs.Replace(htmlString, "");

            //// 处理脱字符转义
            //var regNonMergeParagraphs = new Regex(@"</[pP]>[ \t\r\n]{0,}<[pP]>\\\^", RegexOptions.Multiline );
            //htmlString = regNonMergeParagraphs.Replace(htmlString, "</p><p>^");
            //#endregion

            #region 向前或向后添加自定义 Html Tag's ID or class

            htmlString = AddAttributeToOneHtmlTag(htmlString);
            htmlString = AddSameAttributesToHtmlTags(htmlString);

            #endregion

            #region 添加批阅选择题的按钮
            if (choiceQuestionsCount > 0)
            {
                // 批阅选择题
                var submitAnswersSegment = @"
<table id='stu_info'>
    <tr id='tr_stu_name'><td style='border:none;text-align:right;'>姓名：</td><td style='border:none;'><input id='stu_name' type='text'></td></tr>
    <tr id='tr_stu_grade'>
        <td style='border:none;text-align:right;'>年级：</td>
        <td style='border:none;'>
            <select id='stu_grade'>
                <option value=''>---</option>
                <option value='01'>01年级</option>
                <option value='02'>02年级</option>
                <option value='03'>03年级</option>
                <option value='04'>04年级</option>
                <option value='05'>05年级</option>
                <option value='06'>06年级</option>
                <option value='07'>07年级</option>
                <option value='08'>08年级</option>
                <option value='09'>09年级</option>
            </select>
        </td>
    </tr>
    <tr id='tr_stu_class'>
        <td style='border:none;text-align:right;'>班级：</td>
        <td style='border:none;'>
            <input id='stu_class' type='number'>
        </td>
    </tr>
    <tr id='tr_stu_num'><td style='border:none;text-align:right;'>学号：</td><td style='border:none;'><input id='stu_num' type='number'></td></tr>
</table>
<script>
var stu_name = '';
var stu_grade = '';
var stu_class = '';
var stu_num = '';
function getQueryVariable(variable)
{
    var query = window.location.search.substring(1);
    var vars = query.split(""&"");
    for (var i=0;i<vars.length;i++) {
        var pair = vars[i].split(""="");
        if(pair[0] == variable){
            return pair[1];
        }
    }
    return(false);
}

$(document).ready(function(){
    stu_name = decodeURI(getQueryVariable('stu_name'));
    stu_grade =  decodeURI(getQueryVariable('stu_grade'));
    stu_class = decodeURI(getQueryVariable('stu_class'));
    stu_num = decodeURI(getQueryVariable('stu_num'));

    if(stu_name != 'false' && stu_name != false && stu_name != '' && stu_name != undefined && stu_name != null){
        $('#tr_stu_name').hide();
    }
    if(stu_grade != 'false' && stu_grade != false && stu_grade != '' && stu_grade != undefined && stu_grade != null){
        $('#tr_stu_grade').hide();
    }
    if(stu_class != 'false' && stu_class != false && stu_class != '' && stu_class != undefined && stu_class != null){
        $('#tr_stu_class').hide();
    }
    if(stu_num != 'false' && stu_num != false && stu_num != '' && stu_num != undefined && stu_num != null){
        $('#tr_stu_num').hide();
    }
});
function SubmitAnswers()
{
    var tpaperid = ""{{test_paper_id}}"";
    var stunameval;
    if(stu_name != 'false' & stu_name != false && stu_name != '' && stu_name != undefined && stu_name != null){
        stunameval = stu_name;
    }else{
        stunameval = $('#stu_name').val();
    }
    var stugradeval;
    if(stu_grade != 'false' & stu_grade != false && stu_grade != '' && stu_grade != undefined && stu_grade != null){
        stugradeval = stu_grade;
    }else{
        stugradeval = $('#stu_grade').val();
    }
    var stuclassval;
    if(stu_class != 'false' & stu_class != false && stu_class != '' && stu_class != undefined && stu_class != null){
        stuclassval = stu_class;
    }else{
        stuclassval = $('#stu_class').val();
    }
    var stunumval;
    if(stu_num != 'false' & stu_num != false && stu_num != '' && stu_num != undefined && stu_num != null){
        stunumval = stu_num;
    }else{
        stunumval = $('#stu_num').val();
    }
    var stuid = stugradeval + stuclassval + stunumval;
    if(stunameval == """"){ alert(""请填写姓名"");return;}
    if(stugradeval == """"){ alert(""请选择年级"");return;}
    if(stuclassval == """"){ alert(""请填写班级"");return;}
    if(stunumval == """"){ alert(""请输入学号"");return;}
    var pattern =/[0-9]{1,2}$/ 
    if(pattern.test(stuclassval) == false){ alert(""班级只能是 2 位以内数字！"");return;}
    if(pattern.test(stunumval) == false){ alert(""编号只能是 2 位以内数字！"");return;}
    if(stunumval.length == 1){ stunumval = '0' + stunumval; }
    var stuanswers = '';
    var lis = $(""li[isChecked$='true']"");
    if(lis.length < choiceExCount){
        alert('请做完再提交答案！');
        return;
    }
    for(var i = 0; i < lis.length; i++)
    {
        var li = lis[i];
        var liMark = li.getAttribute('mark');
        stuanswers = stuanswers + liMark;
    }
    stuanswers = stuanswers.replace(/．/g,'');
    var time = new Date();
    var stu_comment = time.toLocaleString();
    var stu_data = {
        stu_id: stuid,
        stu_name: stunameval,
        stu_grade: stugradeval,
        stu_class: stuclassval,
        answers: stuanswers,
        test_paper_id: tpaperid,
        submit_time: stu_comment,
        comment: ''
    };
    $.ajax({
        type: ""post"",
        url: ""{{receiver_url}}"",
        data: stu_data,
        dataType: ""json"",
        crossDomain: true == !(document.all),
        success: function(data) {
            submitted = true;
            alert(stugradeval + "" 年级 "" + stuclassval + "" 班的 "" + stunameval + "" 同学，服务器已收到你提交的如下答案：\n\n"" + stuanswers + ""\n\n请继续努力，做更好的自己！"");
        },
        error: function(err){
            submitted = false;
            alert('提交出错！请稍后重试或联系管理员！');
        }
    });
}
</script>

";

                // 下面这个已不必要了。
                //  <button onclick='SubmitAnswers()' style='text-indent:0px;'>提交答案</button>

                if (Globals.MainWindow.CompileAsTestPaper && forEpub == false)
                {
                    htmlString += $"<script>" +
                        $"var choiceExCount={choiceQuestionsCount};" +
                        $"function percentNum(num1, num2){{return (Math.round(num1/num2*10000)/100.00).toString()+\"%\"}}" +
                        $"function SubmitChoiceExAnswer(){{" +
                            $"if(choiceExCount <= 0){{alert('当前文档中没有选择题！');return;}}" +
                            $"var rightCount=0; " +
                            $"for(i=1;i<=choiceExCount;i++){{" +
                                $"if($('#ex_'+i.toString()).attr('class')=='ex_c')" +
                                $"{{" +
                                    $"if($('#ex_'+i.toString()).attr('isRight')=='true')" +
                                    $"{{" +
                                        $"rightCount++;" +
                                    $"}}" +
                                $"}}" +
                            $"}}" +
                            $"alert(\"共 \"+choiceExCount+\" 道选择题，做对 \"+rightCount+\" 题。正确率：\"+percentNum(rightCount,choiceExCount));" +
                        $"}}" +
                        $"function OpenShowAnswerMode(){{" +
                            $"var lis = $(\"li[isChecked$='true']\");" +
                            $"if(lis.length < choiceExCount)" +
                             "{" +
                                 "alert('请做完再尝试查看答案！');" +
                                 "return;" +
                             "}" +
                            $"if(submitted==false){{" +
                                $"submitted=true;" +      // 此变量在 Question.cs 文件中定义
                                $"alert('请点击选择题的各选项来查看答案与解析！');" +
                            $"}}else{{" +
                                $"submitted=false;" +      // 此变量在 Question.cs 文件中定义
                                $"alert('请点击所有选择题的选项再点击底部【查看正确率】按钮来查看正确率！');" +
                            $"}}" +
                        $"}}" +
                        $"</script>";

                    if (string.IsNullOrWhiteSpace(Globals.MainWindow.AnswerReceiverUrl))
                    {
                        htmlString += "<table style='border:none;'><tr><td style='border:none;width=25%;'><Button onclick='SubmitChoiceExAnswer()' style='text-indent:0;width:100%;'>查看正确率</Button></td>";
                        htmlString += "<td style='border:none;width=25%;'><Button onclick='CopyAnswersLetter()' style='text-indent:0;width:100%;'>复制答案字符串</Button></td>";
                        htmlString += "<td style='border:none;width=25%;'><Button onclick='OpenShowAnswerMode()' style='text-indent:0;width:100%;'>查看选择题答案</Button></td></tr></table>";
                    }
                    else
                    {
                        // 移到前面去，不允许为空
                        //if (string.IsNullOrWhiteSpace(testPaperId))
                        //{
                        //    var time = DateTime.Now;
                        //    testPaperIdNumber++;
                        //    testPaperId = time.Year.ToString("D4") + time.Month.ToString("D2") + time.Day.ToString("D2") + testPaperIdNumber.ToString("D2");
                        //    LMessageBox.Show(
                        //        $"当前工作区开启了下列选项：\r\n\r\n" +
                        //        $"　　　【选择题按试卷样式编译】\r\n" +
                        //        $"　　　【接收选择题答案的网址】\r\n\r\n" +
                        //        $"　　但当前文档没有设置【试卷ID】。\r\n\r\n" +
                        //        $"　　继续编译将自动以【{testPaperId}】作为当前试卷的ID。\r\n\r\n" +
                        //        $"　　〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓\r\n\r\n" +
                        //        $"　　强烈建议手工在文档接近开头的位置按类似下列这样设置试卷ID值：\r\n\r\n" +
                        //        $"　　　；试卷ID：2023030202\r\n\r\n" +
                        //        $"　　这样不仅便于查看试卷ID，而且方便答案上传到服务器后比对答案等操作（您需要自己准备服务器并提供上传数据的接口）。",
                        //        Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information, "AutoSetTestPaperID", null, Globals.MainWindow);
                        //}
                        htmlString += submitAnswersSegment.Replace("{{receiver_url}}", Globals.MainWindow.AnswerReceiverUrl).Replace("{{test_paper_id}}", testPaperId);
                        htmlString += "<table style='border:none;'><tr><td style='border:none;width=25%;'><Button onclick='SubmitChoiceExAnswer()' style='text-indent:0;width:100%;'>查看正确率</Button></td>";
                        htmlString += "<td style='border:none;width=25%;'><Button onclick='SubmitAnswers()' style='text-indent:0;width:100%;'>在线提交答案</Button></td>";
                        htmlString += "<td style='border:none;width=25%;'><Button onclick='OpenShowAnswerMode()' style='text-indent:0;width:100%;'>查看选择题答案</Button></td></tr></table>";
                    }
                }
            }
            #endregion

            htmlString = htmlString.Replace("<!--table class='line_layout_table'><tr class='line_layout_tr'>-->", "<table class='line_layout_table'><tr class='line_layout_tr'>")
                .Replace("<!--td class='line_layout_td' style='width:", "<td class='line_layout_td' style='width:")
                .Replace("'==tdId-->", "'>")
                .Replace("<!--/td class='line_layout_td'-->", "</td>")
                .Replace("<!--/tr class='line_layout_tr'></table class='line_layout_table'>-->", "</tr></table>");

            return javascriptBuilder + menuHtml + htmlString;

            #region 旧版的H1折叠代码
            //List<string> blockStrings = new List<string>();

            //int index1 = 0;
            //int index2 = htmlString.IndexOf("<h1");

            //while (index2 >= 0)
            //{
            //    blockStrings.Add(htmlString.Substring(index1, index2 - index1));
            //    index1 = index2;

            //    index2 = htmlString.IndexOf("<h1", index2 + 1);
            //}

            //if (index1 >= 0)
            //    blockStrings.Add(htmlString.Substring(index1));

            //if (blockStrings.Count > 0)
            //{
            //    #region 添加折叠一级标题的js代码

            //    StringBuilder sBuilder = new StringBuilder();
            //    int panelCount = 0;
            //    if (blockStrings.Count > 1)//第一个成员不是以<H1开头的。
            //    {
            //        javascriptBuilder.Append("<script>");

            //        for (int i = 1; i <= blockStrings.Count; i++)
            //        {
            //            String s = blockStrings[i - 1];

            //            if (s.StartsWith("<h1") == false)
            //            {
            //                sBuilder.Append(s);
            //                continue;
            //            }

            //            int indexOfH1End = s.IndexOf("</h1>");
            //            if (indexOfH1End < 0)
            //            {
            //                sBuilder.Append(s);
            //                continue;
            //            }

            //            sBuilder.Append("<h1 ");
            //            sBuilder.Append("id=\"title" + i + "\"");
            //            sBuilder.Append(s.Substring(3, indexOfH1End + 2));
            //            sBuilder.Append("<div ");
            //            sBuilder.Append("id=\"panel" + i + "\"" + (collapseH1 ? " style=\"display: none;\"" : "") + ">");
            //            sBuilder.Append(s.Substring(indexOfH1End + 5));
            //            sBuilder.Append("</div>");

            //            javascriptBuilder.Append("$(document).ready(function(){"
            //                    + "$(\"#title" + i + "\").click(function(){"
            //                    + "$(\"#panel" + i + "\").toggle();});});");

            //            //在页面加载后再隐藏不好，不如直接就不显示。
            //            //if (collapseH1)
            //            //{
            //            //    javascriptBuilder.Append("$(document).ready(function(){"
            //            //            + "$(\"#panel" + i + "\").toggle();});");
            //            //}

            //            panelCount++;
            //        }
            //    }
            //    #endregion

            //    if (panelCount > 0)
            //    {
            //        return javascriptBuilder + "</script>" + sBuilder.ToString();
            //    }

            //    return javascriptBuilder + htmlString;
            //}
            //else
            //    return javascriptBuilder + htmlString;
            #endregion
        }

        private static int testPaperIdNumber = 0;

        private static string FormatHeaderTagss(string htmlDocumentText)
        {
            NSoup.Nodes.Document doc = NSoup.NSoupClient.Parse(htmlDocumentText);

            var chars = new char[] { ' ', '\n', '\r', };

            var h1Elements = doc.GetElementsByTag("h1");
            foreach (var element in h1Elements)
            {
                element.Html(element.Html().Trim(chars));
            }

            var h2Elements = doc.GetElementsByTag("h2");
            foreach (var element in h2Elements)
            {
                element.Html(element.Html().Trim(chars));
            }

            var h3Elements = doc.GetElementsByTag("h3");
            foreach (var element in h3Elements)
            {
                element.Html(element.Html().Trim(chars));
            }

            var h4Elements = doc.GetElementsByTag("h4");
            foreach (var element in h4Elements)
            {
                element.Html(element.Html().Trim(chars));
            }

            var h5Elements = doc.GetElementsByTag("h5");
            foreach (var element in h5Elements)
            {
                element.Html(element.Html().Trim(chars));
            }

            var h6Elements = doc.GetElementsByTag("h6");
            foreach (var element in h6Elements)
            {
                element.Html(element.Html().Trim(chars));
            }
            return doc.OuterHtml();
        }

        private static string GetTitleFromTail(string tail)
        {
            if (tail != null && tail.ToLower().EndsWith("</p>"))
            {
                tail = tail.Substring(0, tail.Length - 4);
            }

            if (string.IsNullOrWhiteSpace(tail)) return "";

            var index = tail.LastIndexOf("%");
            if (index >= 0) return tail.Substring(index + 1).Trim();

            return "";
        }

        private static object ConvertClassMark(string mark)
        {
            if (string.IsNullOrWhiteSpace(mark)) return "";

            if ("0123456789０１２３４５６７８９０".Contains(mark))
            {
                return $"\n<div class=\"ibox_{FormatNumber(mark)}\"";
            }
            else
            {
                switch (mark)
                {
                    case "?":
                    case "？":
                        {
                            return $" class=\"ibox_question\"";
                        }
                    case "!":
                    case "！":
                        {
                            return $" class=\"ibox_exclamation\">";
                        }
                    case "-":
                    case "－":
                        {
                            return $" class=\"ibox_minus\"";
                        }
                    case "+":
                    case "＋":
                        {
                            return $" class=\"ibox_plus\"";
                        }
                    case "*":
                        {
                            return $" class=\"ibox_star\"";
                        }
                    case "^":
                        {
                            return $" class=\"ibox_caret\"";
                        }
                    default:
                        {
                            return $" class=\"ibox_unknown\"";
                        }
                }
            }
        }

        /// <summary>
        /// 把全角数字字符转换为对应的半角数字字符。
        /// </summary>
        /// <param name="numberText"></param>
        /// <returns></returns>
        private static object FormatNumber(string numberText)
        {
            if (string.IsNullOrWhiteSpace(numberText)) return numberText;

            //return numberText.Replace("０", "0").Replace("１", "1").Replace("２", "2").Replace("３", "3").Replace("４", "4").Replace("５", "5")
            //    .Replace("６", "6").Replace("７", "7").Replace("８", "8").Replace("９", "9");

            var sb = new StringBuilder();
            foreach (var c in numberText)
            {
                var cValue = (int)c;
                if (cValue >= (int)'⒈' && cValue <= (int)'⒛')
                {
                    var newC = (char)((int)c - ((int)'⒈' - (int)'1'));
                    sb.Append(newC);
                }
                else if (cValue >= (int)'㈠' && cValue <= (int)'㈩')
                {
                    var index = "㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩".IndexOf(c);
                    if (index >= 0)
                    {
                        sb.Append("一二三四五六七八九十"[index]);
                    }
                }
                else if (cValue >= (int)'㊀' && cValue <= (int)'㊉')
                {
                    var index2 = "㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉".IndexOf(c);
                    if (index2 >= 0)
                    {
                        sb.Append("①②③④⑤⑥⑦⑧⑨⑩"[index2]);
                    }
                }
                else if (cValue >= (int)'⑴' && cValue <= (int)'⒇')
                {
                    var newC = (char)((int)c - ((int)'⑴' - (int)'1'));
                    sb.Append(newC);
                }
                else if (cValue >= (int)'０' && cValue <= (int)'９')
                {
                    var newC = (char)((int)c - ((int)'０' - (int)'0'));
                    sb.Append(newC);
                }
                else sb.Append(c);
            }

            return sb.ToString();
        }

        /// <summary>
        /// 当一行文本以图像链接开头，并以图像链接结尾时，MarkdownSharp 会忽略它——从而导致图标不能在同一行内。
        /// 为避免此问题，需要手工在头、尾加上段落标记。
        /// </summary>
        /// <param name="htmlString"></param>
        /// <returns></returns>
        private static string FormatMultiImageLinkInOneline(string htmlString)
        {
            if (string.IsNullOrWhiteSpace(htmlString)) return "";

            var lines = htmlString.Split(new char[] { '\n', }, StringSplitOptions.None);

            var sb = new StringBuilder();
            foreach (var line in lines)
            {
                var ln = line.Trim(new char[] { ' ', '　', '\t', });
                var low = ln.ToLower();
                if ((low.StartsWith("<img>") || low.StartsWith("<img ")) && low.EndsWith("/>") &&
                    (low.IndexOf("<img", 5) >= 0 || low.LastIndexOf("/>", 5) >= 0))
                {
                    sb.Append("<p>" + ln + "</p>");
                }
                else
                {
                    sb.Append(line);
                }
                sb.Append("\n");
            }
            return sb.ToString();
        }

        /// <summary>
        /// 将简单样式表转换为 CSS 样式表。
        /// </summary>
        /// <param name="matchStyle"></param>
        /// <param name="withSquareBrackets"></param>
        /// <returns></returns>
        public static string ConvertToCssStyle(Match matchStyle, bool isHideDiv, out string mark, bool withSquareBrackets = false)
        {
            string styleText;
            var trimChars2 = new char[] { ' ', '　', '\t', '\'', '\"', };
            var trimChars3 = new char[] { ' ', '　', '\t', '[', ']', '【', '】', '$', '￥', };
            //var styleSourceText = matchStyle.Value.Trim(trimChars2);
            string styleSourceText;
            var markReg = new Regex(@"^[ 　\t]{0,}?(\d|[\?？]|[!！]|[\-－]|[+＋]|[\*]|\^)?[ 　\t]{0,}?[$￥]");
            var markMatch = markReg.Match(matchStyle.Value);
            if (markMatch.Success)
            {
                styleSourceText = matchStyle.Value.Substring(markMatch.Length).Trim(trimChars2);
                mark = markMatch.Value.Trim(trimChars3);
            }
            else
            {
                styleSourceText = matchStyle.Value.Trim(trimChars2);
                mark = "";
            }

            if (withSquareBrackets)
            {
                styleSourceText = styleSourceText.Trim(trimChars3);
            }

            var stylePieces = Utils.Text.SplitToPiecesByRegex(@"[,，;；]", styleSourceText);
            StringBuilder sbStylePieces = new StringBuilder();
            var foreColor = "";
            var backColor = "";
            var fontSize = "";
            var fontWeight = "";
            var fontStyle = "";
            var textAlign = "";
            var textIndent = "";
            foreach (var tp in stylePieces)
            {
                if (tp.IsMartchText) continue;

                var tpTxt = tp.SourceText.Trim(trimChars2);

                switch (tpTxt.ToLower())
                {
                    case "right":
                    case "右":
                    case "右齐":
                    case "右对齐":
                        {
                            textAlign = "text-align: right;";
                            continue;
                        }
                    case "left":
                    case "左":
                    case "左齐":
                    case "左对齐":
                        {
                            textAlign = "text-align: left;";
                            continue;
                        }
                    case "center":
                    case "中":
                    case "居中":
                    case "中齐":
                    case "中对齐":
                    case "居中齐":
                    case "居中对齐":
                        {
                            textAlign = "text-align: center;";
                            continue;
                        }
                    case "justify":
                    case "两端":
                    case "端对齐":
                    case "两端对齐":
                        {
                            textAlign = "text-align: justify;";
                            continue;
                        }
                }

                Regex regexColor = new Regex(@"^\#[0-9a-fA-F]{6,6}");
                var matchColor = regexColor.Match(tpTxt);
                if (matchColor.Success)
                {
                    if (foreColor == "")
                    {
                        foreColor = tpTxt;
                    }
                    else
                    {
                        if (backColor == "")
                        {
                            backColor = tpTxt;
                        }
                    }
                }
                else
                {
                    var cBrush = BrushManager.GetBrush(tpTxt);

                    if (cBrush != null && cBrush is SolidColorBrush)
                    {
                        var sBrush = cBrush as SolidColorBrush;

                        if (foreColor == "")
                        {
                            foreColor = sBrush.Color.ToString();
                        }
                        else
                        {
                            if (backColor == "")
                            {
                                backColor = sBrush.Color.ToString();
                            }
                        }
                        continue;
                    }
                }

                if (string.IsNullOrWhiteSpace(fontSize))
                {
                    if (TryToReadFontSizeText(tp.SourceText, ref fontSize)) continue;
                }

                if (string.IsNullOrWhiteSpace(fontWeight))
                {
                    if (TryToReadFontWeight(tp.SourceText, ref fontWeight)) continue;
                }

                if (string.IsNullOrWhiteSpace(fontStyle))
                {
                    if (TryToReadFontStyle(tp.SourceText, ref fontStyle)) continue;
                }

                if (string.IsNullOrWhiteSpace(textIndent))
                {
                    if (TryToReadTextIndentStyle(tp.SourceText, ref textIndent)) continue;
                }

                if (string.IsNullOrWhiteSpace(tpTxt) == false)
                {
                    sbStylePieces.Append($"\'{tpTxt}\',");
                }
            }

            if (sbStylePieces.Length > 0 && sbStylePieces[sbStylePieces.Length - 1] == ',')
            {
                sbStylePieces.Remove(sbStylePieces.Length - 1, 1);
            }

            if (string.IsNullOrWhiteSpace(foreColor))
            {
                foreColor = "";
            }
            else
            {
                if (foreColor.Length == 9 && foreColor.StartsWith("#"))
                {
                    foreColor = "#" + foreColor.Substring(3); // #FF00FF00  去除（头2位）透明通道。
                }
                foreColor = "color:" + foreColor + ";";
            }

            if (string.IsNullOrWhiteSpace(backColor))
            {
                backColor = "";
            }
            else
            {
                if (backColor.Length == 9 && backColor.StartsWith("#"))
                {
                    backColor = "#" + backColor.Substring(3); // #FF00FF00  去除（头2位）透明通道。
                }
                backColor = "background:" + backColor + ";";
            }

            var fontFamily = sbStylePieces.ToString().Trim(trimChars2);
            if (string.IsNullOrWhiteSpace(fontFamily) == false)
            {
                fontFamily = $"font-family:\'{fontFamily}\';";
            }
            else
            {
                fontFamily = "";
            }

            styleText = $"{fontFamily}{foreColor}{backColor}{fontSize}{fontWeight}{fontStyle}{textIndent}{textAlign}";
            if (string.IsNullOrWhiteSpace(styleText) == false)
            {
                var hideDivText = isHideDiv ? "display:none;" : "";
                styleText = $" style=\"{styleText}{hideDivText}\"";
            }
            else styleText = "";
            return styleText;
        }

        /// <summary>
        /// 只支持：
        /// ①绝对路径：
        ///     绝对尺寸有px（像素）、pt（点，1pt 相当于 1/72in）、in（英寸）、cm（厘米）、mm（毫米）等
        /// ②相对路径：
        ///     em、%
        /// 不支持 CSS3 中添加的 rem （IE9）以上才支持。
        /// 不支持自定义关键字（各浏览器支持不同）：
        ///     预定义关键字有 xx-small、x-small、small、medium、large、x-large、xx-large，尺寸按顺序依次增大，类似于衣服的尺寸。
        /// </summary>
        /// <param name="sourceText"></param>
        /// <param name="fontSizeText"></param>
        /// <returns></returns>
        private static bool TryToReadFontSizeText(string sourceText, ref string fontSizeText)
        {
            if (string.IsNullOrEmpty(sourceText))
            {
                fontSizeText = "";
                return false;
            }

            sourceText = sourceText.Replace(" ", "").Replace("　", "").Replace("\t", "").Replace("。", ".").Replace("，", "").Replace(",", "").ToLower();

            var reg = new Regex(@"^[0-9\.]{1,}((px)|(pt)|(in)|(cm)|(mm)|(em)|\%)$");
            var m = reg.Match(sourceText);
            if (m.Success == false)
            {
                fontSizeText = "";
                return false;
            }

            fontSizeText = "font-size:" + sourceText + ";line-height:1.1em;";   // 这里的 1.1em 是按照当前元素来计算的，而不是按照 html 元素定义的尺寸。如果不加这个 line-height:1.1em 则会出现多行文本重叠。
            return true;
        }

        /// <summary>
        /// normal 	默认值。定义标准的字符。
        /// bold    定义粗体字符。
        /// bolder  定义更粗的字符。
        /// lighter 定义更细的字符。
        /// 
        ///         100       以下定义由粗到细的字符。400 等同于 normal，而 700 等同于 bold。
        ///         200
        ///         300
        ///         400
        ///         500
        ///         600
        ///         700
        ///         800
        ///         900
        ///     
        /// inherit 规定应该从父元素继承字体的粗细。
        /// </summary>
        /// <param name="sourceText"></param>
        /// <param name="fontWeight"></param>
        /// <returns></returns>
        private static bool TryToReadFontWeight(string sourceText, ref string fontWeight)
        {
            if (string.IsNullOrEmpty(sourceText))
            {
                fontWeight = "";
                return false;
            }

            sourceText = sourceText.Replace(" ", "").Replace("　", "").Replace("\t", "").Replace("。", ".").Replace("，", "").Replace(",", "").Replace(".", "").ToLower();

            string items = "bold/bolder/lighter/100/200/300/400/500/600/700/800/900";
            if (items.Contains(sourceText))
            {
                fontWeight = "font-weight:" + sourceText + ";";
                return true;
            }
            else
            {
                string cnItems = "加粗/更粗/特粗/变细/更细";
                if (cnItems.Contains(sourceText))
                {
                    switch (sourceText)
                    {
                        case "加粗":
                            {
                                sourceText = "bold";
                                break;
                            }
                        case "更粗":
                        case "特粗":
                            {
                                sourceText = "bolder";
                                break;
                            }
                        case "更细":
                            {
                                sourceText = "lighter";
                                break;
                            }
                    }
                    fontWeight = "font-weight:" + sourceText + ";";
                    return true;
                }
                else
                {
                    fontWeight = "";
                    return false;
                }
            }
        }

        /// <summary>
        /// normal 	默认值。浏览器显示一个标准的字体样式。
        /// italic 浏览器会显示一个斜体的字体样式。
        /// oblique 浏览器会显示一个倾斜的字体样式。
        /// inherit 从父元素继承。
        /// </summary>
        /// <param name="sourceText"></param>
        /// <param name="fontStyle"></param>
        /// <returns></returns>
        private static bool TryToReadFontStyle(string sourceText, ref string fontStyle)
        {
            if (string.IsNullOrEmpty(sourceText))
            {
                fontStyle = "";
                return false;
            }

            sourceText = sourceText.Replace(" ", "").Replace("　", "").Replace("\t", "").Replace("。", ".").Replace("，", "").Replace(",", "").Replace(".", "").ToLower();

            string items = "italic/oblique";
            if (items.Contains(sourceText))
            {
                fontStyle = "font-style:" + sourceText + ";";
                return true;
            }
            else
            {
                string cnItems = "斜体/倾斜";
                if (cnItems.Contains(sourceText))
                {
                    switch (sourceText)
                    {
                        case "斜体":
                            {
                                sourceText = "italic";
                                break;
                            }
                        case "倾斜":
                            {
                                sourceText = "oblique";
                                break;
                            }
                    }
                    fontStyle = "font-style:" + sourceText + ";";
                    return true;
                }
                else
                {
                    fontStyle = "";
                    return false;
                }
            }
        }

        private static bool TryToReadTextIndentStyle(string sourceText, ref string fontStyle)
        {
            if (string.IsNullOrEmpty(sourceText))
            {
                fontStyle = "";
                return false;
            }

            sourceText = sourceText.Replace(" ", "").Replace("　", "").Replace("\t", "").Replace("。", ".").Replace("，", "").Replace(",", "").Replace(".", "").ToLower();

            string items = "0indent/0textindent";
            if (items.Contains(sourceText))
            {
                fontStyle = "text-indent:" + sourceText + ";";
                return true;
            }
            else
            {
                string cnItems = "0indent/0textindent/无缩进/取消缩进";
                if (cnItems.Contains(sourceText))
                {
                    fontStyle = "text-indent: 0;";
                    return true;
                }
                else
                {
                    fontStyle = "";
                    return false;
                }
            }
        }

        /// <summary>
        /// 用户可以使用：
        /// ； &lt; (@id tagName) xxx
        /// 这样的语法来向前（或向后）添加找到的第一个（指定 TagName 的）Html Tag 的 ID。
        /// 经过编译后，用户定义的这些标记符会变成下面这样：
        /// "<br class='__custom-single-attribute-mark__' directory='Preview|Next' attributeText='attributeText' tagName='altText'/>"
        /// </summary>
        /// <param name="htmlString"></param>
        /// <returns></returns>
        private static string AddAttributeToOneHtmlTag(string htmlString)
        {
            var pieces = Utils.Text.SplitToPiecesByRegex(@"(<p>)?<br class='__custom-single-attribute-mark__' directory='((AddToPreview)|(AddToNext))' attributeText='.{1,}' tagName='.{0,}'/>(</p>)?", htmlString);
            for (int i = pieces.Count - 1; i >= 0; i--)
            {
                var p = pieces[i];
                if (p.IsMartchText)
                {
                    if (GetSingleAttributeInfo(p.SourceText, out bool? next, out string attributeName, out string attributeValue, out string tagName))
                    {
                        if (tagName == null) tagName = "";

                        if (next.HasValue && next.Value == false)  // 向前一个查找
                        {
                            var prePiece = FindPreViewValidPiece(pieces, i, tagName);
                            if (prePiece != null)
                            {
                                // 向前要倒找
                                Regex regPreviewHtmlTag = new Regex(@"<[a-zA-Z]{1,}[a-zA-Z0-9\-_]{0,}([^<>]{0,}?)/{0,1}>");
                                var matches = regPreviewHtmlTag.Matches(prePiece.SourceText);
                                if (matches.Count > 0)
                                {
                                    var im = matches.Count - 1;
                                    var match = matches[im];
                                    while (match.Value.ToLower().StartsWith("<" + tagName.ToLower()) == false && im >= 1)
                                    {
                                        match = matches[--im];
                                    }

                                    var htmlTag = new HtmlTag(match.Value);
                                    var tagAttributes = htmlTag.Attributes;
                                    if (tagAttributes.ContainsKey(attributeName) == false)
                                    {
                                        htmlTag.AppendDictionary.Add(attributeName, attributeValue);
                                    }
                                    else
                                    {
                                        if (string.IsNullOrWhiteSpace(tagAttributes[attributeName]))
                                        {
                                            tagAttributes[attributeName] = attributeValue;
                                        }
                                    }
                                    prePiece.SourceText =
                                        prePiece.ReplacedText = prePiece.SourceText.Substring(0, match.Index) + htmlTag.ToString() + prePiece.SourceText.Substring(match.Index + match.Length);
                                }
                            }
                        }
                        else  // 向后一个查找
                        {
                            var nextPiece = FindNextValidPiece(pieces, i, tagName);
                            if (nextPiece != null)
                            {
                                Regex regHtmlTag = new Regex(@"<[a-zA-Z]{1,}[a-zA-Z0-9\-_]{0,}([^<>]{0,}?)/{0,1}>");
                                var matches = regHtmlTag.Matches(nextPiece.SourceText);
                                if (matches.Count > 0)
                                {
                                    var im = 0;
                                    var match = matches[im];
                                    while (match.Value.ToLower().StartsWith("<" + tagName.ToLower()) == false && im < matches.Count - 1)
                                    {
                                        match = matches[++im];
                                    }

                                    var htmlTag = new HtmlTag(match.Value);
                                    var tagAttributes = htmlTag.Attributes;
                                    if (tagAttributes.ContainsKey(attributeName) == false)
                                    {
                                        htmlTag.AppendDictionary.Add(attributeName, attributeValue);
                                    }
                                    else
                                    {
                                        if (string.IsNullOrWhiteSpace(tagAttributes[attributeName]))
                                        {
                                            tagAttributes[attributeName] = attributeValue;
                                        }
                                    }

                                    nextPiece.SourceText =
                                        nextPiece.ReplacedText = nextPiece.SourceText.Substring(0, match.Index) + htmlTag.ToString() + nextPiece.SourceText.Substring(match.Index + match.Length);
                                }
                            }
                        }
                    }

                    p.ReplacedText = "";  // 总是要忽略的。
                }
            }

            StringBuilder sb = new StringBuilder();
            foreach (var p in pieces)
            {
                sb.Append(p.ReplacedText);
            }
            return sb.ToString();
        }


        /// <summary>
        /// 用户可以使用：
        /// ； &lt; (&class tagName) xxx
        /// 这样的语法来向前（或向后）添加找到的所有（指定 TagName 的）Html Tag 的 class。
        /// 经过编译后，用户定义的这些标记符会变成下面这样：
        /// "&lt;br class='__custom-attributes-mark__' directory='Preview|Next' attributesText='attributesText' tagName='tagName'/&gt;"
        /// </summary>
        /// <param name="htmlString"></param>
        /// <returns></returns>
        private static string AddSameAttributesToHtmlTags(string htmlString)
        {
            var pieces = Utils.Text.SplitToPiecesByRegex(@"(<p>)?<br class='__custom-attributes-mark__' directory='((AddToPreview)|(AddToNext))' attributesText='.{1,}' forAllTags='.{0,}' tagName='.{0,}'/>(</p>)?", htmlString);
            for (int i = pieces.Count - 1; i >= 0; i--)
            {
                var p = pieces[i];
                if (p.IsMartchText)
                {
                    if (GetAttributesInfo(p.SourceText, out bool? next, out string attributesName, out string attributesValue, out string tagName, out bool forAllTags))
                    {
                        if (string.IsNullOrWhiteSpace(tagName)) return htmlString;
                        var trimChars = new char[] { ' ', '　', '\t', ':', '：', };
                        if (attributesValue.StartsWith(attributesName))
                        {
                            attributesValue = attributesValue.Substring(attributesName.Length).Trim(trimChars);
                        }

                        tagName = tagName.Trim(trimChars);

                        if (next.HasValue && next.Value == false)  // 向前一个非标记片段查找
                        {
                            var prePieces = FindPreViewValidPieceList(pieces, i, tagName);
                            if (prePieces != null && prePieces.Count > 0)
                            {
                                for (int pi = prePieces.Count - 1; pi >= 0; pi--)
                                {
                                    var prePiece = prePieces[pi];
                                    Regex regPreviewHtmlTag = new Regex(@"<[a-zA-Z]{1,}[a-zA-Z0-9\-_]{0,}([^<>]{0,}?)/{0,1}>");
                                    var matches = regPreviewHtmlTag.Matches(prePiece.SourceText);
                                    if (matches.Count > 0)
                                    {
                                        for (int im = matches.Count - 1; im >= 0; im--)  // 一定要逆序
                                        {
                                            var match = matches[im];
                                            if (match.Value.ToLower().StartsWith("<" + tagName.ToLower()))
                                            {
                                                var htmlTag = new HtmlTag(match.Value);
                                                var tagAttributes = htmlTag.Attributes;
                                                if (tagAttributes.ContainsKey(attributesName) == false)
                                                {
                                                    htmlTag.AppendDictionary.Add(attributesName, attributesValue);
                                                }
                                                else
                                                {
                                                    var newDict = new Dictionary<string, string>();
                                                    foreach (var key in tagAttributes.Keys)
                                                    {
                                                        if (key.ToLower() == attributesName.ToLower())
                                                        {
                                                            if (string.IsNullOrWhiteSpace(tagAttributes[attributesName]))
                                                            {
                                                                newDict.Add(attributesName, attributesValue);
                                                            }
                                                            else
                                                            {
                                                                newDict.Add(attributesName, tagAttributes[attributesName] + (" " + attributesValue));
                                                            }
                                                        }
                                                        else
                                                        {
                                                            newDict.Add(key, tagAttributes[key]);
                                                        }
                                                    }
                                                    htmlTag.Attributes = newDict;
                                                }
                                                var replacedTag = htmlTag.ToString();

                                                prePiece.SourceText =
                                                    prePiece.ReplacedText = prePiece.ReplacedText.Substring(0, match.Index) + replacedTag + prePiece.ReplacedText.Substring(match.Index + match.Length);

                                                if (forAllTags == false) break;  // 只添加找到的最后一个
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        else  // 向后一个非标记片段查找
                        {
                            var nextPieces = FindNextValidPieceList(pieces, i, tagName);
                            if (nextPieces != null && nextPieces.Count > 0)
                            {
                                for (int pi = nextPieces.Count - 1; pi >= 0; pi--)
                                {
                                    var nextPiece = nextPieces[pi];
                                    Regex regHtmlTag = new Regex(@"<[a-zA-Z]{1,}[a-zA-Z0-9\-_]{0,}([^<>]{0,}?)/{0,1}>");
                                    var matches = regHtmlTag.Matches(nextPiece.SourceText);
                                    if (matches.Count > 0)
                                    {
                                        if (forAllTags == false)
                                        {
                                            for (int im = 0; im <= matches.Count - 1; im++)  // 不要逆序
                                            {
                                                var match = matches[im];
                                                if (match.Value.ToLower().StartsWith("<" + tagName.ToLower()))
                                                {
                                                    var htmlTag = new HtmlTag(match.Value);
                                                    var tagAttributes = htmlTag.Attributes;
                                                    if (tagAttributes.ContainsKey(attributesName) == false)
                                                    {
                                                        htmlTag.AppendDictionary.Add(attributesName, attributesValue);
                                                    }
                                                    else
                                                    {
                                                        var newDict = new Dictionary<string, string>();
                                                        foreach (var key in tagAttributes.Keys)
                                                        {
                                                            if (key.ToLower() == attributesName.ToLower())
                                                            {
                                                                if (string.IsNullOrWhiteSpace(tagAttributes[attributesName]))
                                                                {
                                                                    newDict.Add(attributesName, attributesValue);
                                                                }
                                                                else
                                                                {
                                                                    newDict.Add(attributesName, tagAttributes[attributesName] + (" " + attributesValue));
                                                                }
                                                            }
                                                            else
                                                            {
                                                                newDict.Add(key, tagAttributes[key]);
                                                            }
                                                        }
                                                        htmlTag.Attributes = newDict;
                                                    }
                                                    var replacedTag = htmlTag.ToString();

                                                    nextPiece.SourceText =
                                                        nextPiece.ReplacedText = nextPiece.ReplacedText.Substring(0, match.Index) + replacedTag + nextPiece.ReplacedText.Substring(match.Index + match.Length);
                                                    break; // forAllTags == false 时，只加一个就跳出
                                                }
                                            }
                                        }
                                        else
                                        {
                                            for (int im = matches.Count - 1; im >= 0; im--)  // 一定要逆序
                                            {
                                                var match = matches[im];
                                                if (match.Value.ToLower().StartsWith("<" + tagName.ToLower()))
                                                {
                                                    var htmlTag = new HtmlTag(match.Value);
                                                    var tagAttributes = htmlTag.Attributes;
                                                    if (tagAttributes.ContainsKey(attributesName) == false)
                                                    {
                                                        htmlTag.AppendDictionary.Add(attributesName, attributesValue);
                                                    }
                                                    else
                                                    {
                                                        var newDict = new Dictionary<string, string>();
                                                        foreach (var key in tagAttributes.Keys)
                                                        {
                                                            if (key.ToLower() == attributesName.ToLower())
                                                            {
                                                                if (string.IsNullOrWhiteSpace(tagAttributes[attributesName]))
                                                                {
                                                                    newDict.Add(attributesName, attributesValue);
                                                                }
                                                                else
                                                                {
                                                                    newDict.Add(attributesName, tagAttributes[attributesName] + (" " + attributesValue));
                                                                }
                                                            }
                                                            else
                                                            {
                                                                newDict.Add(key, tagAttributes[key]);
                                                            }
                                                        }
                                                        htmlTag.Attributes = newDict;
                                                    }
                                                    var replacedTag = htmlTag.ToString();

                                                    nextPiece.SourceText =
                                                        nextPiece.ReplacedText = nextPiece.ReplacedText.Substring(0, match.Index) + replacedTag + nextPiece.ReplacedText.Substring(match.Index + match.Length);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    p.ReplacedText = "";  // 总是要忽略的。
                }
            }

            StringBuilder sb = new StringBuilder();
            foreach (var p in pieces)
            {
                sb.Append(p.ReplacedText);
            }
            return sb.ToString();
        }

        private static TextPiece FindPreViewValidPiece(List<TextPiece> pieces, int fromPieceIndex, string tagName)
        {
            if (pieces.Count <= 0) return null;
            if (fromPieceIndex <= 0) return null;
            if (fromPieceIndex >= pieces.Count) return null;

            for (int i = fromPieceIndex - 1; i >= 0; i--)
            {
                var piece = pieces[i];
                if (piece.IsMartchText == false && piece.SourceText.LastIndexOf("<" + tagName) >= 0) return piece;
            }

            return null;
        }

        private static TextPiece FindNextValidPiece(List<TextPiece> pieces, int fromPieceIndex, string tagName)
        {
            if (pieces.Count <= 0) return null;
            if (fromPieceIndex < 0) return null;
            if (fromPieceIndex >= pieces.Count - 1) return null;

            for (int i = fromPieceIndex + 1; i < pieces.Count; i++)
            {
                var piece = pieces[i];
                if (piece.IsMartchText == false && piece.SourceText.LastIndexOf("<" + tagName) >= 0) return piece;
            }

            return null;
        }

        private static List<TextPiece> FindPreViewValidPieceList(List<TextPiece> pieces, int fromPieceIndex, string tagName)
        {
            if (pieces.Count <= 0) return null;
            if (fromPieceIndex <= 0) return null;
            if (fromPieceIndex >= pieces.Count) return null;

            var list = new List<TextPiece>();

            for (int i = fromPieceIndex - 1; i >= 0; i--)
            {
                var piece = pieces[i];
                if (piece.SourceText.StartsWith("<p><br class='__custom-attributes-mark__'") && piece.SourceText.IndexOf($"tagName='{tagName}'") >= 0)
                    break;

                if (piece.SourceText.LastIndexOf("<" + tagName) >= 0)
                    list.Add(piece);
            }

            return list;
        }

        private static List<TextPiece> FindNextValidPieceList(List<TextPiece> pieces, int fromPieceIndex, string tagName)
        {
            if (pieces.Count <= 0) return null;
            if (fromPieceIndex < 0) return null;
            if (fromPieceIndex >= pieces.Count - 1) return null;

            var list = new List<TextPiece>();

            for (int i = fromPieceIndex + 1; i < pieces.Count; i++)
            {
                var piece = pieces[i];
                if (piece.SourceText.StartsWith("<p><br class='__custom-attributes-mark__'") && piece.SourceText.IndexOf($"tagName='{tagName}'") >= 0)
                    break;

                if (piece.SourceText.LastIndexOf("<" + tagName) >= 0)
                    list.Add(piece);
            }

            return list;
        }

        private static bool GetSingleAttributeInfo(string src, out bool? next, out string attributeName, out string attributeValue, out string tagName)
        {
            if (string.IsNullOrWhiteSpace(src))
            {
                next = null;
                attributeName = null;
                attributeValue = null;
                tagName = null;
                return false;
            }

            if (src.StartsWith("<p>") && src.EndsWith("</p>")) src = src.Substring(3, src.Length - 7);
            if (string.IsNullOrWhiteSpace(src))
            {
                next = null;
                attributeName = null;
                attributeValue = null;
                tagName = null;
                return false;
            }

            var tag = new HtmlTag(src);
            if (tag.TagType == HtmlTagType.UnKnow || string.IsNullOrWhiteSpace(src))
            {
                next = null;
                attributeName = null;
                attributeValue = null;
                tagName = null;
                return false;
            }

            try
            {
                var trimChars = new char[] { ' ', '　', '\t', };
                var attributes = tag.Attributes;
                var attributeText = attributes["attributeText"];
                attributeName = "";
                attributeValue = "";
                var splitIndex = attributeText.IndexOf(":");
                if (splitIndex >= 0)
                {
                    attributeName = attributeText.Substring(0, splitIndex).Trim(trimChars);
                    if (string.IsNullOrWhiteSpace(attributeName))
                        attributeName = "id";
                    attributeValue = attributeText.Substring(splitIndex + 1).Trim(trimChars);
                }
                else
                {
                    attributeName = "id";  // 默认为 id
                    attributeValue = attributeText.Trim(trimChars);
                }

                tagName = attributes["tagName"];

                next = true;
                var nextText = attributes["directory"];
                if (nextText != null && nextText != "AddToNext")
                {
                    next = false;
                }

                return true;
            }
            catch (Exception ex)
            {
                next = null;
                attributeName = null;
                attributeValue = null;
                tagName = null;
                Console.Write(ex.Message);
                return false;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="src"></param>
        /// <param name="next"></param>
        /// <param name="attributeName"></param>
        /// <param name="attributeValue"></param>
        /// <param name="tagName"></param>
        /// <param name="forAllTags">是否向所有同名 tag 添加。如为 false，则仅应向最近的一个添加。</param>
        /// <returns></returns>
        private static bool GetAttributesInfo(string src, out bool? next, out string attributeName, out string attributeValue, out string tagName, out bool forAllTags)
        {
            if (string.IsNullOrWhiteSpace(src))
            {
                next = null;
                attributeName = null;
                attributeValue = null;
                tagName = null;
                forAllTags = true;
                return false;
            }

            if (src.StartsWith("<p>") && src.EndsWith("</p>")) src = src.Substring(3, src.Length - 7);
            if (string.IsNullOrWhiteSpace(src))
            {
                next = null;
                attributeName = null;
                attributeValue = null;
                tagName = null;
                forAllTags = true;
                return false;
            }

            var tag = new HtmlTag(src);
            if (tag.TagType == HtmlTagType.UnKnow || string.IsNullOrWhiteSpace(src))
            {
                next = null;
                attributeName = null;
                attributeValue = null;
                tagName = null;
                forAllTags = true;
                return false;
            }

            try
            {
                var trimChars = new char[] { ' ', '　', '\t', ':', '：' };
                var attributes = tag.Attributes;
                var attributeText = attributes["attributesText"];
                attributeName = "";
                attributeValue = "";
                var splitIndex = attributeText.Replace("：", ":").IndexOf(":");
                if (splitIndex >= 0)
                {
                    attributeName = attributeText.Substring(0, splitIndex).Trim(trimChars);
                    if (string.IsNullOrWhiteSpace(attributeName))
                        attributeName = "class";
                    attributeValue = attributeText.Substring(splitIndex + 1).Trim(trimChars);
                }
                else
                {
                    attributeName = "class";  // 默认为 class
                    attributeValue = attributeText.Trim(trimChars);
                }

                tagName = attributes["tagName"];

                next = true;
                var nextText = attributes["directory"];
                if (nextText != null && nextText != "AddToNext")
                {
                    next = false;
                }

                if (attributeText.StartsWith(attributeName))
                {
                    attributeText = attributeText.Substring(attributeName.Length).Trim(trimChars);
                }

                var regTagName = new Regex(@"[ 　\t]*[a-zA-Z][a-zA-Z0-9\-_]*?[ 　\t]*$");
                var matchTagName = regTagName.Match(tagName);
                if (matchTagName.Success)
                {
                    tagName = matchTagName.Value.Trim(trimChars);
                }

                var forAllTagsText = attributes["forAllTags"];
                if (forAllTagsText != null && forAllTagsText.ToLower() == "true")
                {
                    forAllTags = true;
                }
                else forAllTags = false;
                return true;
            }
            catch (Exception ex)
            {
                next = null;
                attributeName = null;
                attributeValue = null;
                tagName = null;
                forAllTags = true;
                Console.Write(ex.Message);
                return false;
            }
        }

        /// <summary>
        /// 代码块中，"\r\n"会导致多一个换行。
        /// </summary>
        /// <param name="htmlString"></param>
        /// <returns></returns>
        private static string FormatPreCodeBlock(string htmlString)
        {
            //为什么不用正则？这因为 \r 在某些情况下似乎是有用的：与 IE 的安全提示消息有关。
            //var reg = new Regex(@"(((\r\n){2,})|((\r\r){2,})|((\n\n){2,}))", RegexOptions.Multiline);
            //return reg.Replace(htmlString, "\n");
            return htmlString.Replace("\n\n", "\n");
        }

        private static string FormatInlieCodeBlock(string htmlString)
        {
            if (string.IsNullOrWhiteSpace(htmlString)) return htmlString;

            var pieces = Utils.Text.SplitToPiecesByRegex(@"(?<=([<][cC][oO][dD][eE][>])|([<][cC][oO][dD][eE][ ]{1,}.{0,}?[>])).*?(?=[<][/][cC][oO][dD][eE][>])", htmlString);

            // |  &#124;
            StringBuilder sb = new StringBuilder();
            var regPipeText = new Regex(@"");
            foreach (var piece in pieces)
            {
                if (piece.IsMartchText == false)
                {
                    sb.Append(piece.SourceText);
                    continue;
                }

                var subPieces = Utils.Text.SplitToPiecesByRegex(@"(&amp;){1,}\#((124)|(65372));", piece.SourceText);
                var subSb = new StringBuilder();
                foreach (var subPiece in subPieces)
                {
                    if (subPiece.IsMartchText)
                    {
                        if (subPiece.SourceText.StartsWith("&amp;&amp;"))
                        {
                            if (subPiece.SourceText.EndsWith("#124;"))
                            {
                                subSb.Append("&amp;#124;");
                            }
                            else
                            {
                                subSb.Append("&amp;#65372;");
                            }
                        }
                        else
                        {
                            if (subPiece.SourceText.EndsWith("#124;"))
                            {
                                subSb.Append("|");
                            }
                            else
                            {
                                subSb.Append("｜");
                            }
                        }
                    }
                    else
                    {
                        subSb.Append(subPiece.SourceText);
                    }
                }
                sb.Append(subSb);
            }

            return sb.ToString();
        }

        /// <summary>
        /// 解决 URL 中特殊字符的转义问题。
        /// 1. +        URL 中 + 号表示空格                      %2B
        /// 2. 空格     URL中的空格可以用 + 号或者编码           %20
        /// 3. /        分隔目录和子目录                         %2F
        /// 4. ?        分隔实际的 URL 和参数                    %3F
        /// 5. %        指定特殊字符                             %25
        /// 6. #        表示书签                                 %23 
        /// 7. &        URL 中指定的参数间的分隔符               %26
        /// 8. =        URL 中指定参数的值                       %3D
        /// </summary>
        /// <param name="value">待转义的相对路径。</param>
        /// <returns>转义后的相对路径。</returns>
        public static string UrlEncode(string value)
        {
            var pieces = value.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
            StringBuilder sb = new StringBuilder();
            foreach (var s in pieces)
            {
                // % 必须第一个替换。
                sb.Append(s.Replace("%", "%25").Replace("+", "%2B").Replace(" ", "%20").Replace("?", "%3F")
                    .Replace("#", "%23").Replace("&", "%26").Replace("=", "%3D") + "/");
            }

            var result = sb.ToString();
            if (value.StartsWith("/") || value.StartsWith("\\"))
            {
                result = "/" + result;
            }

            if (value.EndsWith("/") || value.EndsWith("\\"))
            {
                result += "/";
            }
            else
            {
                if (result.EndsWith("/") || result.EndsWith("\\"))
                {
                    result = result.Substring(0, result.Length - 1);
                }
            }

            return result;
        }

        /// <summary>
        /// 取 Html 标题的层级，并传出去除标记符之外的文本。
        /// </summary>
        /// <param name="htmlHeaderSpan">形如：“<h1>abc</h1>”这样的文本。</param>
        /// <param name="headerText">用以返回此级标题的内容文本（即 Html 首尾标签之间的文本）。</param>
        /// <returns></returns>
        public static int GetHtmlHeaderLevel(string htmlHeaderSpan, out string headerText)
        {
            if (string.IsNullOrWhiteSpace(htmlHeaderSpan))
            {
                headerText = "";
                return 0;
            }

            var reg = new Regex(@"\>.*\</");
            Match match = reg.Match(htmlHeaderSpan);
            if (match != null && match.Success)
            {
                headerText = match.Value.Substring(1, match.Length - 3);
            }
            else
            {
                headerText = "";
            }

            if (htmlHeaderSpan.StartsWith("<h1") || htmlHeaderSpan.StartsWith("<H1")) return 1;
            if (htmlHeaderSpan.StartsWith("<h2") || htmlHeaderSpan.StartsWith("<H2")) return 2;
            if (htmlHeaderSpan.StartsWith("<h3") || htmlHeaderSpan.StartsWith("<H3")) return 3;
            if (htmlHeaderSpan.StartsWith("<h4") || htmlHeaderSpan.StartsWith("<H4")) return 4;
            if (htmlHeaderSpan.StartsWith("<h5") || htmlHeaderSpan.StartsWith("<H5")) return 5;
            if (htmlHeaderSpan.StartsWith("<h6") || htmlHeaderSpan.StartsWith("<H6")) return 6;

            return 0;
        }

        /// <summary>
        /// 取任务列表项的标记文本（头文本）。
        /// 例如：返回 “[+] 这是任务列表项”的“[+] ”。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <returns></returns>
        public static string GetHeaderOfTaskListItem(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return "";

            var tmp = lineText.Replace(" ", "").Replace("－", "-").Replace("　", "").Replace("\t", "");//括弧间允许空白字符存在，所以不用TrimStart()。
            if (tmp.ToLower().StartsWith("[-]") || tmp.ToLower().StartsWith("[%]") ||
                tmp.ToLower().StartsWith("[+]") || tmp.ToLower().StartsWith("[#]"))
            {
                if (tmp.Length >= 4)
                {
                    if (tmp[3] == '(')
                    {
                        if (tmp.Substring(4).Contains(')'))
                        {
                            //[]()形如这样是文字链接。
                            return "";
                        }
                    }
                }

                return tmp.Substring(0, 3) + " ";
            }
            return "";
        }

        /// <summary>
        /// 取六级标题的正则表达式。
        /// </summary>
        /// <param name="level"></param>
        /// <returns></returns>
        public static string GetTieleRegByLevel(int level)
        {
            switch (level)
            {
                case 1: return "^[ 　]{0,3}[#＃][^#＃]";
                case 2: return "^[ 　]{0,3}[#＃]{2}[^#＃]";
                case 3: return "^[ 　]{0,3}[#＃]{3}[^#＃]";
                case 4: return "^[ 　]{0,3}[#＃]{4}[^#＃]";
                case 5: return "^[ 　]{0,3}[#＃]{5}[^#＃]";
                case 6: return "^[ 　]{0,3}[#＃]{6}[^#＃]";
                default: return "";
            }
        }

        /// <summary>
        /// 判断此行文本是否“region”区域的分割线行。格式是：... region xxx ...。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsRegionSplitter(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex reg = new Regex(@"^[ 　]{0,3}[.。]{3,}[ 　\t]{0,}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}.{0,}?[.。]{3,}[ 　\t]{0,}$");
            var match = reg.Match(lineText);
            if (match != null && match.Success)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断指定的文本是不是 Header 标题。格式是：“###xxx”。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsHeaderLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex reg = new Regex(@"^[ 　]{0,3}[#＃].*$");
            var match = reg.Match(lineText);
            if (match != null && match.Success)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行文本是否 TODO Comment。格式是：“; TODO: xxx”。
        /// </summary>
        /// <param name="lineText"></param>
        /// <returns></returns>
        public static bool IsTodoCommentLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex regex = new Regex(@"^[   ]{0,3}[;；]([tTＴｔ][oOｏＯ][dDｄＤ][oOｏＯ])?[:：]");
            var match = regex.Match(lineText);
            if (match != null && match.Success)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行文本是否 TODO Comment。格式类似：“; TODO: xxx”或“; DONE：xxx”等。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <param name="todoCommentMark">用以传出 TODO Comment 的标记文本。</param>
        /// <param name="newTodoCommentTail">用以传出 TODO Comment 的内容文本（除标记以外的、有意义的文本）。</param>
        /// <param name="mark">传入“TODO”（或“DONE”等）。</param>
        public static bool IsTodoCommentLine(string lineText, out string todoCommentMark, out string newTodoCommentTail, string mark)
        {
            newTodoCommentTail = lineText;
            todoCommentMark = "";
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex regex;
            switch (mark)
            {
                case "DOING":
                    {
                        regex = new Regex(@"^[   ]{0,3}[;；]([dDｄＤ][oOｏＯ][iIｉＩ][nNｎＮ][gGｇＧ])?[:：]");
                        break;
                    }
                case "DONE":
                    {
                        regex = new Regex(@"^[   ]{0,3}[;；]([dDｄＤ][oOｏＯ][nNｎＮ][eEｅＥ])?[:：]");
                        break;
                    }
                default:
                    {
                        regex = new Regex(@"^[   ]{0,3}[;；]([tTＴｔ][oOｏＯ][dDｄＤ][oOｏＯ])?[:：]");
                        break;
                    }
            }

            var match = regex.Match(lineText);
            if (match != null && match.Success)
            {
                todoCommentMark = match.Value;
                newTodoCommentTail = " " + lineText.Substring(match.Length).Trim(new char[] { ' ', '　', '\t' });
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行文本是否编译菜单链接的指示标记文本行。格式是：“; [Menu]: xxx”。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <param name="newTodoCommentTail">用以传出 [Menu]: 后的内容文本（除标记以外的、可能有意义的注释文本）。</param>
        public static bool IsMenuMarkLine(string lineText, out string newMenuMarkTail)
        {
            newMenuMarkTail = lineText;
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex regex = new Regex(@"^[   ]{0,3}[;；](\[?(([mMｍＭ][eEｅＥ][nNｎＮ][uUｕＵ])|(菜单))\]?)[:：]");
            var match = regex.Match(lineText);
            if (match != null && match.Success)
            {
                newMenuMarkTail = " " + lineText.Substring(match.Length).Trim(new char[] { ' ', '　', '\t' });
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行文本是否用于指示当前文档演示模式的文本行。格式是：“；PM：xxx”。
        /// </summary>
        /// <param name="lineText"></param>
        /// <param name="newPresentationTypeTail"></param>
        /// <returns></returns>
        public static bool IsPresentationTypeLine(string lineText, out string newPresentationTypeTail)
        {
            newPresentationTypeTail = lineText;
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex regex = new Regex(@"^[   ]{0,3}[;；](([pPｐＰ][mMｍＭ])|(演示(模式)?))[:：]");
            var match = regex.Match(lineText);
            if (match != null && match.Success)
            {
                newPresentationTypeTail = " " + lineText.Substring(match.Length).Trim(new char[] { ' ', '　', '\t' });
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行文本是否用双冒号或双带逗号加粗行头的行。格式是：“xxx：：yyy”或者“xxx，，yyy”→其中“xxx”部分默认应用加粗格式。
        /// </summary>
        /// <param name="lineText">行广西。</param>
        /// <param name="dblMark">例如： [:：] 或者 [,，] 或者 。 或者 [?？] 或者 [!！]</param>
        /// <param name="boldHeaderLineHeader">用于返回标记左侧的有效文本。</param>
        /// <param name="boldHeaderLineTail">用于返回标记右侧的有效文本。</param>
        /// <param name="formattedMark">返回格式化后的标记文本。双冒号/双逗号/双句号不需要这个，但惊叹号和问号应支持最多三个。</param>
        /// <returns></returns>
        public static bool IsBoldHeaderLine(string lineText, string dblMark, out string boldHeaderLineHeader, out string boldHeaderLineTail, out string formattedMark)
        {
            boldHeaderLineHeader = lineText;
            boldHeaderLineTail = "";
            formattedMark = "";

            var attention = "";
            if (dblMark == "[!！]")
            {
                attention = "(注意[：:])?";
            }

            if (string.IsNullOrWhiteSpace(lineText)) return false;
            if (lineText.StartsWith("\t") || lineText.Replace("　", " ").StartsWith("    ")) return false;

            Regex regexBoldHeader = new Regex(@"^[ 　]{0,3}" + attention + @"[\u4e00-\u9fa5A-Za-zａ-ｚＡ-Ｚ'""“‘’”0-9０-９\(\)《〈〉》、．·—\-][\u4e00-\u9fa5A-Za-zａ-ｚＡ-Ｚ'""“‘’”0-9０-９ 　\(\)《〈〉》、．·—\-]{1,}" + dblMark + @"{2,}[ 　\t]{0,}");
            var matchBoldHeader = regexBoldHeader.Match(lineText);
            if (matchBoldHeader.Success)
            {
                var trimChars1 = new char[] { ' ', '　', '\t', ':', };
                var otherTrimChars = dblMark.Trim(new char[] { '[', ']', });
                var oldLength = trimChars1.Length;
                Array.Resize(ref trimChars1, trimChars1.Length + otherTrimChars.Length);
                for (int i = oldLength; i < trimChars1.Length; i++)
                {
                    trimChars1[i] = otherTrimChars[i - oldLength];
                }
                var trimChars2 = new char[] { ' ', '　', '\t', };
                boldHeaderLineHeader = matchBoldHeader.Value.Trim(trimChars1);
                boldHeaderLineTail = lineText.Substring(matchBoldHeader.Length).Trim(trimChars2);

                for (var i = matchBoldHeader.Value.Length - 1; i >= 0; i--)
                {
                    var c = matchBoldHeader.Value[i];
                    if (otherTrimChars.Contains(c))
                    {
                        formattedMark += c;
                    }
                }

                formattedMark = formattedMark.Replace("!", "！").Replace("?", "？").Replace(",", "，").Replace(":", "：");  // 句号不支持半角，不转换
                return true;
            }

            return false;
        }

        /// <summary>
        /// 这个只适合连续句号、叹号、问号标记。格式是：“xxx。。”或者“xxx！！”或者“xxx？？”，其中问号、叹号可以多于2个。
        /// </summary>
        public static bool IsBoldHeaderLine2(string lineText, string dblMark, out string boldHeaderLineHeader, out string boldHeaderLineTail, out string formattedMark)
        {
            boldHeaderLineHeader = lineText;
            boldHeaderLineTail = "";
            formattedMark = "";

            var attention = "";
            if (dblMark == "[!！]")
            {
                attention = "(注意[：:])?";
            }

            if (string.IsNullOrWhiteSpace(lineText)) return false;
            if (lineText.StartsWith("\t") || lineText.Replace("　", " ").StartsWith("    ")) return false;

            //Regex regexBoldHeader = new Regex(@"^[ 　]{0,3}" + attention + @"[^?？!！。]{1,}" + dblMark + @"{2,}[ 　\t]{0,}");
            Regex regexBoldHeader = new Regex(@"^[ 　]{0,3}" + attention + @".{1,}" + dblMark + @"{2,}[ 　\t]{0,}");
            var matchBoldHeader = regexBoldHeader.Match(lineText);
            if (matchBoldHeader.Success)
            {
                var trimChars1 = new char[] { ' ', '　', '\t', ':', };
                var otherTrimChars = dblMark.Trim(new char[] { '[', ']', });
                var oldLength = trimChars1.Length;
                Array.Resize(ref trimChars1, trimChars1.Length + otherTrimChars.Length);
                for (int i = oldLength; i < trimChars1.Length; i++)
                {
                    trimChars1[i] = otherTrimChars[i - oldLength];
                }
                var trimChars2 = new char[] { ' ', '　', '\t', };
                boldHeaderLineHeader = matchBoldHeader.Value.Trim(trimChars1);
                boldHeaderLineTail = lineText.Substring(matchBoldHeader.Length).Trim(trimChars2);

                for (var i = matchBoldHeader.Value.Length - 1; i >= 0; i--)
                {
                    var c = matchBoldHeader.Value[i];
                    if (otherTrimChars.Contains(c))
                    {
                        formattedMark += c;
                    }
                }

                formattedMark = formattedMark.Replace("!", "！").Replace("?", "？").Replace(",", "，").Replace(":", "：");  // 句号不支持半角，不转换
                return true;
            }

            return false;
        }

        /// <summary>
        /// 判断此行是否被嵌入到 引用块 中的 Header 标题。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <param name="match">正则匹配对象。</param>
        public static bool IsHeaderInBlockQuote(string lineText, out Match match)
        {
            if (string.IsNullOrWhiteSpace(lineText))
            {
                match = null;
                return false;
            }

            Regex reg = new Regex(@"^[ 　]{0,3}[>〉》][>〉》 　][#＃]{1,6}");
            match = reg.Match(lineText);
            if (match != null && match.Success)
            {
                return true;
            }

            match = null;
            return false;
        }

        /// <summary>
        /// 取任务列表项的内容文本（即形如：“[-]xxx”中的“xxx”部分。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static string GetContentOfTaskListItem(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return "";

            var tmp = lineText.Replace(" ", "").Replace("－", "-").Replace("　", "").Replace("\t", "");//括弧间允许空白字符存在，所以不用TrimStart()。
            if (tmp.ToLower().StartsWith("[-]") || tmp.ToLower().StartsWith("[%]") ||
                tmp.ToLower().StartsWith("[+]") || tmp.ToLower().StartsWith("[#]"))
            {
                if (tmp.Length >= 4)
                {
                    if (tmp[3] == '(')
                    {
                        if (tmp.Substring(4).Contains(')'))
                        {
                            //[]()形如这样是文字链接。
                            return "";
                        }
                    }
                }

                return tmp.Substring(3).TrimStart(new char[] { ' ' });
            }
            return "";
        }

        /// <summary>
        /// 此方法用于在二维文字表的单元格文本、任务列表项的正文文本、材料出处文本、
        /// 放大显示的文本行、自定义折叠区的首尾文本、步骤标记的正文文本、冒号开头的注释文本……等自定义元素中支持 Markdown 基本格式语法（加粗、倾斜等）
        /// 此方法受制于 Globals.MainWindow.EnableBaseMDSyntax 属性的值，这个属性的值记录在工作区而不是应用程序的配置文件中，默认值为 false，即不开启。
        /// 之所以默认不开启，是因为对过去的旧文件可能造成破坏。
        /// </summary>
        /// <returns></returns>
        public static String FormatLineContent(ref MarkdownSharp.Markdown md, string srcLineContent, bool replaceParaSplitter = false)
        {
            if (string.IsNullOrEmpty(srcLineContent)) return "";
            if (md == null) return srcLineContent;

            if (Globals.MainWindow.EnableBaseMDSyntax == false) return srcLineContent;

            //加“正文”两个字的目的是防止解析为引用块、列表项等特殊块文本。
            //只要你喜欢，换成其它普通文本也是可以的——只要别让 MarkdownSharp 的转换器发疯就可以。
            var resultContent = md.Transform("正文" + srcLineContent.Trim(new char[] { ' ', '　' }));
            if (resultContent.ToLower().StartsWith("<p>正文")) resultContent = resultContent.Substring(5);
            else if (resultContent.ToLower().StartsWith("<p>")) resultContent = resultContent.Substring(3);
            if (resultContent.ToLower().EndsWith("</p>")) resultContent = resultContent.Substring(0, resultContent.Length - 4);
            else if (resultContent.ToLower().EndsWith("</p>\n")) resultContent = resultContent.Substring(0, resultContent.Length - 5);

            if (replaceParaSplitter)
            {
                resultContent = resultContent.Replace("<para-splitter/>", "</p><p class='para_header_tail_paragraph'>");
            }

            return resultContent;
        }

        /// <summary>
        /// 【注意】这个方法是给 IronPython 调用的。
        /// 应在应用标准 Markdown 编译器之前调用，且应先调用 RemoveExtraLines()。
        /// 支持一些 LME 自定义的功能（例如二维文字表）。
        /// </summary>
        /// <param name="markdownText"></param>
        /// <returns></returns>
        public static String PreCompile(string markdownText, ref int choiceQuestionsCount, bool forEpub = false, string test_paper_id = "", string markdownFilePath = "")
        {
            string[] lines;
            lines = CustomMarkdownSupport.FormatMarkdownText(markdownText).Replace("\r\n", "\n")
                .Split(new char[] { '\n', }, StringSplitOptions.None);//要保留空行

            #region 以惊叹号开头的行被视为树型文字表行，树型文字表默认以代码方式显示
            //要防止单独占一行的图像链接文本和以惊叹号开头的自定义折叠区。
            //注意：这个格式化不放在 FormatMarkdown()方法中。因为编译时才需要格式化，编辑时不需要格式化。
            for (int x = 0; x < lines.Length; x++)
            {
                if (IsTreeListTextLine(lines[x], out string header, out string tail, out int level))
                {
                    if (IsImageLinkLine(lines[x])) continue;
                    // LME 内部调用在 RemoveExtraLines 方法中
                    string treeLineHeader;

                    if (header.Length > 1)
                    {
                        treeLineHeader = "！" + new string('　', header.Length - 1);
                    }
                    else { treeLineHeader = ""; }

                    var newLine = Regex.Replace(lines[x], @"\<br[ 　\t]*?/?\>", $"\n    {treeLineHeader}", RegexOptions.IgnoreCase);
                    lines[x] = "    " + newLine;
                }
            }

            // 必须重建 lines 数组
            lines = string.Join("\n", lines).Split(new char[] { '\n' }, StringSplitOptions.None);
            #endregion

            #region 忽略行首空白字符（双句点标记）

            // 对以两个句点开头的行进行处理，自动忽略两个句点及其后的空白（全半角空格、Tab）字符。
            // 这是为了解决不支持 Html 源文本缩进的问题——因为正常情况下，一缩进就变成代码块了。
            // 为什么是两个句点呢？这是为了保持 Html 缩进与 Tab 缩进的对齐——奇数个很难对齐，4个又输入起来又太麻烦——2个刚刚好。

            var trimChars = new char[] { ' ', '　', '\t', };

            for (int x = 0; x < lines.Length; x++)
            {
                if (lines[x].StartsWith("..") && IsRegionSplitter(lines[x]) == false)
                {
                    var txt = lines[x].Substring(2);
                    lines[x] = txt.TrimStart(trimChars);
                }
            }

            #endregion 

            markdownText = string.Join("\n", lines);

            var dbl_mark_index = 0;

            return PreCompile(markdownText, 1, Globals.MainWindow.AutoNumberStyle, false, new char[] { '\n', }, ref choiceQuestionsCount, ref dbl_mark_index, "", forEpub, test_paper_id, markdownFilePath);
        }

        public static bool IsSquareStart(string line)
        {
            if (string.IsNullOrWhiteSpace(line)) return false;

            var reg = new Regex("^[ 　]{0,3}\\[");
            var match = reg.Match(line);
            return match.Success;
        }

        public static bool IsSquareEnd(string line)
        {
            if (string.IsNullOrWhiteSpace(line)) return false;

            var reg = new Regex(@"^[ 　]{0,3}\\]");
            var match = reg.Match(line);
            return match.Success;
        }

        /// <summary>
        /// 此段摘抄自：<http://www.oicqzone.com/pc/2015083122336.html>
        /// HTML中&nbsp; &ensp; &emsp; &thinsp;等6种空白空格的区别
        /// 
        /// HTML提供了5种空格实体（space entity），它们拥有不同的宽度，非断行空格（&nbsp;）是常规空格的宽度，可运行于所有主流浏览器。其他几种空格（ &ensp; &emsp; &thinsp; &zwnj;&zwj;）在不同浏览器中宽度各异。
        /// 
        /// &nbsp;
        /// 它叫不换行空格，全称No-Break Space，它是最常见和我们使用最多的空格，大多数的人可能只接触了&nbsp;，它是按下space键产生的空格。在HTML中，如果你用空格键产生此空格，空格是不会累加的（只算1个）。要使用html实体表示才可累加，该空格占据宽度受字体影响明显而强烈。
        /// 
        /// &ensp;        
        /// 它叫“半角空格”，全称是En Space，en是字体排印学的计量单位，为em宽度的一半。根据定义，它等同于字体度的一半（如16px字体中就是8px）。名义上是小写字母n的宽度。此空格传承空格家族一贯的特性：透明的，此空格有个相当稳健的特性，就是其占据的宽度正好是1/2个中文宽度，而且基本上不受字体影响。
        /// 
        /// &emsp;
        /// 它叫“全角空格”，全称是Em Space，em是字体排印学的计量单位，相当于当前指定的点数。例如，1 em在16px的字体中就是16px。此空格也传承空格家族一贯的特性：透明的，此空格也有个相当稳健的特性，就是其占据的宽度正好是1个中文宽度，而且基本上不受字体影响。
        /// 
        /// &thinsp;        
        /// 它叫窄空格，全称是Thin Space。我们不妨称之为“瘦弱空格”，就是该空格长得比较瘦弱，身体单薄，占据的宽度比较小。它是em之六分之一宽。
        /// 
        /// &zwnj; 
        /// 它叫零宽不连字，全称是Zero Width Non Joiner，简称“ZWNJ”，是一个不打印字符，放在电子文本的两个字符之间，抑制本来会发生的连字，而是以这两个字符原本的字形来绘制。Unicode中的零宽不连字字符映射为“”（zero width non-joiner，U+200C），HTML字符值引用为： &#8204;
        /// 
        /// &zwj;
        /// 它叫零宽连字，全称是Zero Width Joiner，简称“ZWJ”，是一个不打印字符，放在某些需要复杂排版语言（如阿拉伯语、印地语）的两个字符之间，使得这两个本不会发生连字的字符产生了连字效果。零宽连字符的Unicode码位是U+200D (HTML: &#8205; &zwj;）。
        /// 
        /// 此外，浏览器还会把以下字符当作空白进行解析：空格（&#x0020;）、制表位（&#x0009;）、换行（&#x000A;）和回车（&#x000D;）还有（&#12288;）等等。
        /// </summary>

        /// <summary>
        /// 预处理markdown文本，这样可以支持表格、块引用预处理等。
        ///
        /// ｜表格标题｜ ｜列标题1｜列标题2｜列标题3｜
        /// ｜：－－－－：｜－－：｜：－－｜ 
        /// ｜列1、行1｜列2、行1｜列3、行1｜
        /// ｜列1、行2｜列2、行2｜列3、行3｜
        /// </summary>
        /// <param name="markdownText">待转换的源 Markdown 文本。</param>
        /// <param name="separatorText">用于分割文本行的分割符。这是考虑文本文件可能来自于不同的操作系统。</param>
        /// <param name="startH1Number">用以表示H1应从什么值开始编号。这是为跨文件接续编号准备的。</param>
        /// <param name="forbiddenAutoNumber">强行禁止自动编号。</param>
        /// <returns></returns>
        private static String PreCompile(string markdownText, int startH1Number, string autoNumberStyle,
            bool forbiddenAutoNumber, char[] separatorText, ref int choiceQuestionsCount, ref int dbl_mark_index, string toWorkspacePrefix = "",
            bool forEpub = false, string text_paper_id = "", string markdownFilePath = "")
        {
            if (markdownText == null || markdownText.Length == 0)
            {
                return markdownText;
            }

            // 合并行标记本在此处处理是有问题的，有些元素需要在 RemoveExtraLines() 方法中处理，故合并行标记应在调用 RemoveExtraLines 方法之前。
            //var regexJoinLines = new Regex(@"(((\r\n)|\n)[ 　\t]{0,}){0,}((\r\n)|\n)\^[ 　\t]{1,}", RegexOptions.Multiline);
            //markdownText = regexJoinLines.Replace(markdownText, "");

            // 脱字符在行首转义在下面处理
            markdownText = Question.ConvertQuestionsToHtml(markdownText, ref choiceQuestionsCount,
                Globals.MainWindow.miRemoveScripts.IsChecked, forEpub, text_paper_id, markdownFilePath);//先将试题文本转换为html标签。
            markdownText = markdownText.Replace("\r\n", "\n");
            String[] lineStrings = markdownText.Split(separatorText, StringSplitOptions.None);

            #region 提前转换无序列表

            // 原始 Markdown 的无序列表没有区分度

            for (int i = 0; i < lineStrings.Length; i++)
            {
                var line = lineStrings[i];
                if (IsHorizontalLineText(line)) continue;

                var regStarsStart = new Regex(@"^((([\*\-\+][ 　\t]{1,}){1,})|(([\-\+][ 　\t]{0,}){1,}))");
                var matchStarsStart = regStarsStart.Match(line);
                if (matchStarsStart.Success)
                {
                    var prefixStars = new StringBuilder();
                    foreach (char c in matchStarsStart.Value)
                    {
                        if ("-".Contains(c)) prefixStars.Append("- <!--clean li-->");
                        else if ("+".Contains(c)) prefixStars.Append("+ <!--default li-->");
                        else if ("*".Contains(c)) prefixStars.Append("- <!--custom li-->");
                    }

                    lineStrings[i] = prefixStars + line.Substring(matchStarsStart.Length);
                }
            }

            // 编译结束后，NSoup 会根据 html 注释来决定添加何种 class
            #endregion

            #region 格式化引用块
            for (int i = 0; i < lineStrings.Length; i++)
            {
                var text = lineStrings[i];
                int markIndex = -1;
                bool isBlockQuote = true;
                if (string.IsNullOrEmpty(text))
                {
                    isBlockQuote = false;
                }
                else
                {
                    for (int j = 0; j < text.Length; j++)
                    {
                        var c = text[j];
                        if (c == ' ' || c == '　') continue;
                        else if (c == '>' || c == '〉' || c == '》')
                        {
                            markIndex = j;
                            continue;
                        }

                        isBlockQuote = false;
                        break;
                    }
                }

                if (isBlockQuote && markIndex >= 0 && markIndex <= 3)
                {
                    var ts = lineStrings[i].TrimStart(new char[] { '\t', ' ', '　' });
                    if (ts.StartsWith("》 ") || ts.StartsWith("》　") || ts.StartsWith("〉 ") || ts.StartsWith("〉　"))
                    {
                        lineStrings[i] = "> " + ts.Substring(2);
                    }
                    else if (ts.StartsWith("》") || ts.StartsWith("〉") || ts.StartsWith(">"))
                    {
                        lineStrings[i] = "> " + ts.Substring(1);
                    }
                }
            }
            #endregion

            MarkdownSharp.Markdown md = new MarkdownSharp.Markdown();

            #region 格式化表格
            List<TableLinesInfo> tableSourcesList = CompileTextTable(lineStrings, ref md);
            #endregion 转换二维文字表

            int regionHeaderNumber = 0;  //这两个用来处理自定义折叠区
            int regionTailNumber = 0;

            Stack<int> panelLayerStack = new Stack<int>();   //此变量只用于记录自定义折叠区嵌套的层数，不是直接的序列

            #region 处理六级标题，自动添加数字序号
            var levelIndex1 = startH1Number - 1;
            var levelIndex2 = 0;
            var levelIndex3 = 0;
            var levelIndex4 = 0;
            var levelIndex5 = 0;
            var levelIndex6 = 0;
            #endregion

            var regC = new Regex(@"^\\\^");  // 要考虑用户想保留脱字符后紧跟的 TAB 符的情况

            // 拼接。
            StringBuilder sBuilder = new StringBuilder();
            int imageIndex = 1;
            var spanNumber = 0;  // 这个用于在冒号注释中支持自动编号。
            var region_split_area_indexes = new Stack<int>();    // 用于处理自定义折叠区内部分横向子区时子区的 Id class
            var region_ids = new Stack<string>();
            // var dbl_mark_index = 0;  // 改 ref 式参数：双句号、双问号、双惊叹号面板的序号，用于写 id
            for (int index = 0; index < lineStrings.Length; index++)
            {
                if (string.IsNullOrWhiteSpace(lineStrings[index])) continue;

                // 脱字符在行首时的转义
                var matchC = regC.Match(lineStrings[index]);
                if (matchC.Success)
                    lineStrings[index] = lineStrings[index].Substring(1);  // 这里不应格式化

                //处理删除线标记（代码块中不转变）。
                if (lineStrings[index].StartsWith("    ") == false && lineStrings[index].StartsWith("\t") == false)
                {
                    lineStrings[index] = lineStrings[index].Replace("[=", "<s>").Replace("[＝", "<s>").Replace("【＝", "<s>").Replace("【=", "<s>")
                        .Replace("=]", "</s>").Replace("＝】", "</s>").Replace("=】", "</s>").Replace("＝]", "</s>")
                        .Replace("[\\=", "[=").Replace("[\\＝", "[=").Replace("【\\＝", "[=").Replace("【\\=", "[=")
                        .Replace("=\\]", "=]").Replace("＝\\】", "=]").Replace("=\\】", "=]").Replace("＝\\]", "=]");
                }

                if (CustomMarkdownSupport.IsCodeBlockLine(lineStrings[index]) == false &&
                    CustomMarkdownSupport.IsTreeListTextLine(lineStrings[index]) == false &&
                    CustomMarkdownSupport.IsHeaderLine(lineStrings[index]) == false)
                {
                    lineStrings[index] = lineStrings[index].Replace("“", "<span class='dbl_quote'>“").Replace("”", "”</span>")
                                                           .Replace("‘", "<span class='sgl_quote'>‘").Replace("’", "’</span>");
                }

                #region 抢先处理图像链接中的多媒体链接

                var mediaPieces = Utils.Text.SplitToPiecesByRegex(@"!\[.*?\]\(.*?\)", lineStrings[index]);
                var sbLine = new StringBuilder();
                foreach (var piece in mediaPieces)
                {
                    if (piece.IsMartchText)
                    {
                        var rLink = ResourceLink.BuildResourceLink(piece.SourceText);
                        if (rLink == null)
                        {
                            sbLine.Append(piece.SourceText);
                        }
                        else
                        {
                            switch (rLink.Type)
                            {
                                case ResourceLinkType.Sound:
                                    if (Globals.MainWindow.DefaultEncoding == "utf-8")
                                    {
                                        // utf-8 编码用 audio 标签
                                        sbLine.Append("<audio class=\"player\" src=\"" + rLink.FilePath + "\" " + rLink.AppendInfo.Trim(new char[] { '\"', }) + "/>");
                                    }
                                    else  // 其它用 IE 的 Embed 元素
                                    {
                                        sbLine.Append("<div class=\"vedio\"><embed class=\"player\" src=\"" + rLink.FilePath + "\" " + rLink.AppendInfo.Trim(new char[] { '\"', }) + "/></div>");
                                    }
                                    break;
                                case ResourceLinkType.Vedio:
                                    if (Globals.MainWindow.DefaultEncoding == "utf-8")
                                    {
                                        // utf-8 编码用 audio 标签
                                        sbLine.Append("<vedio class=\"player\" src=\"" + rLink.FilePath + "\" " + rLink.AppendInfo.Trim(new char[] { '\"', }) + "/>");
                                    }
                                    else  // 其它用 IE 的 Embed 元素
                                    {
                                        sbLine.Append("<div class=\"vedio\"><embed class=\"player\" src=\"" + rLink.FilePath + "\" " + rLink.AppendInfo.Trim(new char[] { '\"', }) + "/></div>");
                                    }
                                    break;
                                default:
                                    sbLine.Append(piece.SourceText);
                                    break;
                            }
                        }
                    }
                    else
                    {
                        sbLine.Append(piece.SourceText);
                    }
                }
                lineStrings[index] = sbLine.ToString();

                #endregion

                #region 抢先处理 QuickImageLink 快捷链接
                if (IsCodeBlockLine(lineStrings[index]) == false)
                {
                    var pieces = Utils.Text.SplitToPiecesByRegex(@"[\(（][:：].*?[:：][\)）]", lineStrings[index]);
                    foreach (var piece in pieces)
                    {
                        if (piece.IsMartchText)
                        {
                            var tmp = piece.SourceText.Trim(new char[] { ' ', '　', '\t', '（', '(', ':', '：', ')', '）', '/', '\\', }).Replace("\\", "/xox");
                            if (tmp.EndsWith("."))
                                tmp += "png";       // 允许省略 png 后缀名。
                            piece.ReplacedText = "<img src=\"" + toWorkspacePrefix + (forEpub ? "../Images~/" : "Images~/") + tmp + "\" is_quick_image=\"true\"/>";
                        }
                    }
                    lineStrings[index] = Utils.Text.JoinReplacedList(pieces);
                }
                #endregion

                #region 处理单独成行的图像链接文本（单行图像链接）
                var trimChars = new char[] { ' ', '　', '\t' };
                //对于单独一行的图像链接，自动添加“图 x 图像标题”这样的文本
                if (IsImageLinkLine(lineStrings[index]))
                {
                    var regex = new Regex(@"(?<=(!\[)).*?(?=\])");
                    var match = regex.Match(lineStrings[index]);
                    if (match.Success && string.IsNullOrEmpty(match.Value) == false)
                    {
                        var imgTitle = match.Value.Trim(trimChars);

                        if (imgTitle == "^")
                        {
                            imgTitle = "";   // 只有一个 ^ 时，表示只显示“图x”字样，文件也是自动命名的——这只适用于 Mini 模式——这是为了便于快速截图。
                        }
                        else
                        {
                            imgTitle = " " + imgTitle;
                        }

                        var groupLinkText = "";


                        // 处理图像链接组（导图链接、复合链接）
                        if (IsImageGroupLinkLine(lineStrings[index], out string word1, out string path, out string word2))
                        {
                            var regGroupFileName = new Regex(@"\{%.*?%\}");
                            var matchGroupFileName = regGroupFileName.Match(path);
                            if (matchGroupFileName.Success)
                            {
                                imgTitle = (string.IsNullOrWhiteSpace(word1) ? "" : " " + word1);
                                //<img src="test~/Images~/WDDTZ--light--.png" alt="我的导图组" class="lme_hide_when_dark" />
                                var lightPath1 = path.Substring(0, matchGroupFileName.Index) + matchGroupFileName.Value.Substring(2, matchGroupFileName.Length - 4) + "--light--" + path.Substring(matchGroupFileName.Index + matchGroupFileName.Length);
                                groupLinkText += "\r\n<img src=\"" + lightPath1 + "\" alt=\"" + word1 + "\" class=\"lme_hide_when_dark\"";
                                if (string.IsNullOrWhiteSpace(word2))
                                {
                                    groupLinkText += "/>\r\n";
                                }
                                else
                                {
                                    groupLinkText += " title=\"" + word2 + "\"/>\r\n";
                                }

                                var lightPath2 = path.Substring(0, matchGroupFileName.Index) + matchGroupFileName.Value.Substring(2, matchGroupFileName.Length - 4) + "--dark--" + path.Substring(matchGroupFileName.Index + matchGroupFileName.Length);
                                groupLinkText += "\r\n<img src=\"" + lightPath2 + "\" alt=\"" + word1 + "\" class=\"lme_hide_when_light\"";
                                if (string.IsNullOrWhiteSpace(word2))
                                {
                                    groupLinkText += "/>\r\n";
                                }
                                else
                                {
                                    groupLinkText += " title=\"" + word2 + "\"/>\r\n";
                                }
                            }
                            else
                            {
                                var regStyle = new Regex(@"^style=[""'].*[""']$", RegexOptions.IgnoreCase);
                                var matchStyle = regStyle.Match(word2);
                                if (matchStyle.Success)
                                {
                                    var formattedLink = "![" + word1 + "](" + path;
                                    if (string.IsNullOrWhiteSpace(word2))
                                    {
                                        formattedLink += ")";
                                    }
                                    else
                                    {
                                        formattedLink += " \"" + word2.Replace("\"", "'").Trim(new char[] { '\"', }) + "\")";
                                    }

                                    lineStrings[index] = formattedLink;
                                }
                            }
                        }

                        if (string.IsNullOrEmpty(groupLinkText))
                        {
                            if (Globals.MainWindow.ImageTitleAtTop)
                            {
                                sBuilder.Append($"<p style=\"text-indent:0px;text-align:center;font-weight:bold;margin-top:1em;margin-bottom:0.2em;\">图{imageIndex++}{imgTitle}</p>\r\n{lineStrings[index]}");
                            }
                            else
                            {
                                sBuilder.Append($"{lineStrings[index]}\r\n<p style=\"text-indent:0px;text-align:center;font-weight:bold;margin-top:0.2em;margin-bottom:1em;\">图{imageIndex++}{imgTitle}</p>");
                            }
                        }
                        else
                        {
                            if (Globals.MainWindow.ImageTitleAtTop)
                            {
                                sBuilder.Append($"<p style=\"text-indent:0px;text-align:center;font-weight:bold;margin-top:1em;margin-bottom:0.2em;\">图{imageIndex++}{imgTitle}</p>\r\n{groupLinkText}");
                            }
                            else
                            {
                                sBuilder.Append($"{groupLinkText}\r\n<p style=\"text-indent:0px;text-align:center;font-weight:bold;margin-top:0.2em;margin-bottom:1em;\">图{imageIndex++}{imgTitle}</p>");
                            }
                        }

                        sBuilder.Append("\r\n");
                        continue;
                    }
                }
                #endregion

                #region 处理简单格式六级伪标题（或称虚拟标题——virtual title——实质是些段落）
                var regSimpleHeader = new Regex(@"^[\^][#＃]{1,}");
                var matchSimpleHeader = regSimpleHeader.Match(lineStrings[index]);
                if (matchSimpleHeader.Success)
                {
                    var level = matchSimpleHeader.Length - 1;
                    if (level > 6)
                    {
                        level = 6;
                    }
                    var levelTxt = level.ToString();
                    sBuilder.Append($"<p class='-lme-title-{levelTxt}'>{lineStrings[index].Substring(matchSimpleHeader.Length).Trim(new char[] { ' ', '　', '\t', '#', '＃', })}</p>\r\n");
                    continue;
                }
                #endregion

                #region 处理六级标题，自动添加数字序号
                if (Globals.MainWindow.AutoNumberHeaders && forbiddenAutoNumber == false)
                {
                    string titleHeader;
                    string titleTail;
                    var titleLevel = HeaderLevel(lineStrings[index], out titleHeader, out titleTail);

                    //不需要在这里考虑是否编译纯净（Clean）版的标题，在AppendProcess中自然会添加class='clean'。
                    switch (titleLevel)
                    {
                        case 1:
                            {
                                levelIndex1++;
                                levelIndex2 = levelIndex3 = levelIndex4 = levelIndex5 = levelIndex6 = 0;
                                lineStrings[index] = "<h1>" + //titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6,
                                    autoNumberStyle) + titleTail + "</h1>";   //不宜开放Markdown基本格式，因为<code></code>块的填空效果与标题的折叠展开操作冲突。
                                break;
                            }
                        case 2:
                            {
                                levelIndex2++;
                                levelIndex3 = levelIndex4 = levelIndex5 = levelIndex6 = 0;
                                lineStrings[index] = "<h2>" + //titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6,
                                    autoNumberStyle) + titleTail + "</h2>";   //不宜开放Markdown基本格式，因为<code></code>块的填空效果与标题的折叠展开操作冲突。
                                break;
                            }
                        case 3:
                            {
                                levelIndex3++;
                                levelIndex4 = levelIndex5 = levelIndex6 = 0;
                                lineStrings[index] = "<h3>" + //titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6,
                                    autoNumberStyle) + titleTail + "</h3>";   //不宜开放Markdown基本格式，因为<code></code>块的填空效果与标题的折叠展开操作冲突。
                                break;
                            }
                        case 4:
                            {
                                levelIndex4++;
                                levelIndex5 = levelIndex6 = 0;
                                lineStrings[index] = "<h4>" + //titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6,
                                    autoNumberStyle) + titleTail + "</h4>";   //不宜开放Markdown基本格式，因为<code></code>块的填空效果与标题的折叠展开操作冲突。
                                break;
                            }
                        case 5:
                            {
                                levelIndex5++;
                                levelIndex6 = 0;
                                lineStrings[index] = "<h5>" + //titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6,
                                    autoNumberStyle) + titleTail + "</h5>";   //不宜开放Markdown基本格式，因为<code></code>块的填空效果与标题的折叠展开操作冲突。
                                break;
                            }
                        case 6:
                            {
                                levelIndex6++;
                                lineStrings[index] = "<h6>" + //titleHeader +
                                    BuildHeaderIndexText(levelIndex1,
                                    levelIndex2,
                                    levelIndex3,
                                    levelIndex4,
                                    levelIndex5,
                                    levelIndex6,
                                    autoNumberStyle) + titleTail + "</h6>";   //不宜开放Markdown基本格式，因为<code></code>块的填空效果与标题的折叠展开操作冲突。
                                break;
                            }
                    }
                }
                #endregion

                #region 处理自定义折叠区域
                Regex regexStart = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}?)?([！？!?ＩｉＷｗＥｅＱｑIiWwEeQq][ 　\t]*)?\{[ 　\t]*[\+\-＋－＝\=\*]{0,1}");
                var matchStart = regexStart.Match(lineStrings[index]);
                if (matchStart != null && matchStart.Success && lineStrings[index].Substring(matchStart.Length).IndexOf('}') < 0)
                {
                    region_split_area_indexes.Push(0);

                    string collapseSelf = null;
                    string always_display_header = "";
                    if ("+＋".Contains(matchStart.Value[matchStart.Length - 1]))
                        collapseSelf = "true";
                    else if ("-－".Contains(matchStart.Value[matchStart.Length - 1]))
                        collapseSelf = "false";
                    else if ("*".Contains(matchStart.Value[matchStart.Length - 1]))       // 单行折叠区，默认折叠
                    {
                        collapseSelf = "single-line-collapse";
                    }
                    else if ("＝=".Contains(matchStart.Value[matchStart.Length - 1]))       // 单行折叠区，默认展开
                    {
                        collapseSelf = "single-line-expand";
                        always_display_header = " alwaysdisplay='true'";
                    }

                    regionHeaderNumber++;//要先加才能保持尾部索引一致
                    panelLayerStack.Push(regionHeaderNumber);
                    var regionHeaderText = lineStrings[index].Substring(matchStart.Length);
                    var headerRegionStyleText = "";
                    var cleanRegionHeaderText = regionHeaderText.Replace("　", " ");
                    if (cleanRegionHeaderText.StartsWith(";") || cleanRegionHeaderText.StartsWith("；"))
                    {
                        cleanRegionHeaderText = "";
                    }

                    if (string.IsNullOrWhiteSpace(cleanRegionHeaderText))
                    {
                        headerRegionStyleText = "border-bottom-color:transparent;border-bottom-width:0;";
                        regionHeaderText = "";//不再采用两个全角空格强制显示的办法，这样更节省空间。
                    }
                    else
                    {
                        var tmpHeader = regionHeaderText.Trim(new char[] { ' ', '　', '\t', }).Replace("：", ":");
                        if (tmpHeader.EndsWith(":"))
                        {
                            if (tmpHeader.StartsWith(":"))
                            {
                                // 两侧都有冒号，居中对齐
                                // 显式指定必须居中，忽略 CSS 文件中的定义。
                                headerRegionStyleText = "text-align:center;";
                            }
                            else
                            {
                                // 只有右侧有冒号，右对齐
                                // 显式指定右对齐，忽略 CSS 文件中的定义。
                                headerRegionStyleText = "text-align:right;";
                            }
                        }
                        else
                        {
                            if (tmpHeader.StartsWith(":"))
                            {
                                // 显示指定左对齐，忽略 CSS 文件中的定义。
                                headerRegionStyleText = "text-align:left;";
                            }
                            //else headerRegionStyleText = ""; 
                            //左右都没写冒号，则 CSS 文件决定——这是默认方式。
                        }

                        regionHeaderText = regionHeaderText.Trim(new char[] { ' ', '　', '\t', '：', ':', });
                    }

                    headerRegionStyleText += "padding-left: 1em;";
                    headerRegionStyleText += "padding-right: 1em;";

                    var regionClassText = "region";
                    Regex regexStartI = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?([ＩｉIi][ 　\t]{0,})\{[\+\-＋－＝\=\*]{0,1}");
                    if (regexStartI.Match(lineStrings[index]).Success)
                    {
                        //headerRegionStyleText += "font-weight : bold;";  // 移动到对应主题样式表中
                        //headerRegionStyleText += "text-indent: 0px;";
                        headerRegionStyleText += "text-align: left;";
                        regionClassText = "region_i";
                    }

                    Regex regexStartW = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?([ＷｗWw!！][ 　\t]{0,})\{[\+\-＋－＝\=\*]{0,1}");
                    if (regexStartW.Match(lineStrings[index]).Success)
                    {
                        //headerRegionStyleText += "font-weight : bold;";  // 移动到对应主题样式表中
                        //headerRegionStyleText += "text-indent: 0px;";
                        headerRegionStyleText += "text-align: left;";
                        regionClassText = "region_w";
                    }

                    Regex regexStartE = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?([ＥｅEe][ 　\t]{0,})\{[\+\-＋－＝\=\*]{0,1}");
                    if (regexStartE.Match(lineStrings[index]).Success)
                    {
                        //headerRegionStyleText += "font-weight : bold;";  // 移动到对应主题样式表中
                        //headerRegionStyleText += "text-indent: 0px;";
                        headerRegionStyleText += "text-align: left;";
                        regionClassText = "region_e";
                    }

                    Regex regexStartQ = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?([ＱｑQq?？][ 　\t]{0,})\{[\+\-＋－＝\=\*]{0,1}");
                    if (regexStartQ.Match(lineStrings[index]).Success)
                    {
                        //headerRegionStyleText += "font-weight : bold;";  // 移动到对应主题样式表中
                        //headerRegionStyleText += "text-indent: 0px;";
                        headerRegionStyleText += "text-align: left;";
                        regionClassText = "region_q";
                    }

                    headerRegionStyleText = $"style='{headerRegionStyleText}'";

                    var regionPanelStyle = "";
                    if (forEpub)
                    {
                        // ★注意：单行折叠区在导出的 Epub 电子书中效果很差，所以已经改成直接处理成 <div>...</div> 了。
                        //         但是这一段不能移除！！！——不然这些变量会出错导致异常。
                        if (collapseSelf == "single-line-collapse" || collapseSelf == "single-line-expand")
                        {
                            // 这两个都表示当前是单行折叠区，所以主面板无内容。
                            regionPanelStyle = $" style='display:none;'";   // 在导出 Epub 电子书时，不会导出 JS 脚本，单行折叠区无内容，不应展开主面板。
                        }
                        else
                        {
                            // 这些都不是单行折叠区，由用户保证存在内容，总是展开的（即使用户没有写内容，导出Epub时也应展开）！！！
                            regionPanelStyle = $" style='display:block;'";   // 在导出 Epub 电子书时，总是可以显示的——因为不会导出 JavaScript。
                        }
                        headerRegionStyleText = $" style='display:block;'";  // 导出 Epub 电子书时，标头文本总是显示的。
                    }
                    else
                    {
                        if (string.IsNullOrWhiteSpace(regionHeaderText.Trim(trimChars)) == false)
                        {
                            if (collapseSelf == null)
                            {
                                if (Globals.MainWindow.TryToCollapseCustomRegion)
                                {
                                    regionPanelStyle = $" style='display:none;'";
                                }
                                else
                                {
                                    regionPanelStyle = $" style='display:block;'";
                                }
                            }
                            else
                            {
                                // 对于单行折叠区，用户写的是：{+ xxx } ，但会自动转换成 {* xxx } ，而这里在处理 {* xxx } 时，区分导出 Epub 和正常生成网页两种情况：
                                // 导出 Epub 时，只显示标头文本（因为单行折叠区主面板无内容）；
                                // 生成 正常网页 时，仍会被视为 {+ xxx }
                                // 这样设计有些复杂，原因是：
                                // 生成正常的网页时，js 会自动判断主面板中有无内容；而生成 Epub 使用的 xhtml 时，不会嵌入 JS 脚本。
                                // 这样设计的好处是：不必改变用户习惯——用户仍然只需要输入：{+ xxx } 即可——编译器自然知道哪个是单行折叠区。
                                if (collapseSelf == "true" || collapseSelf == "single-line-collapse" || collapseSelf == "single-line-expand")
                                {
                                    regionPanelStyle = $" style='display:none;'";  // regionPanelStyle 只管主折叠面板——单行折叠区总应该折叠主面板！
                                }
                                else if (collapseSelf == "false")
                                {
                                    regionPanelStyle = $" style='display:block;'";
                                }
                            }
                        }
                    }

                    var trimMarks = new char[] { ' ', '　', '\t', '(', ')', '（', '）', '@', '&', };


                    int region_split_area_index = -1;
                    if (region_split_area_indexes.Count > 0)
                    {
                        region_split_area_index = region_split_area_indexes.Pop();
                        region_split_area_index++;
                        region_split_area_indexes.Push(region_split_area_index);
                    }

                    var divID = "";
                    var divClass = "";
                    var regId = new Regex(@"(?<=([\(（][@]))[a-zA-Z][a-zA-Z0-9\-_]*?(?=([\)）]))");
                    var matchId = regId.Match(regionHeaderText);
                    if (matchId.Success)
                    {
                        divID = matchId.Value.Trim(trimChars);
                        regionHeaderText = regionHeaderText.Substring(0, matchId.Index).Trim(trimMarks) + regionHeaderText.Substring(matchId.Index + matchId.Length).Trim(trimMarks);
                    }
                    var regClass = new Regex(@"(?<=([\(（][&]))[a-zA-Z][a-zA-Z0-9\-_]*?(?=([\)）]))");
                    var matchClass = regClass.Match(regionHeaderText);
                    if (matchClass.Success)
                    {
                        divClass = " " + matchClass.Value.Trim(trimMarks);
                        regionHeaderText = regionHeaderText.Substring(0, matchClass.Index).Trim(trimMarks) + regionHeaderText.Substring(matchClass.Index + matchClass.Length).Trim(trimMarks);
                    }

                    region_ids.Push(divID);

                    var regCollapseMark = new Regex(@"^[ 　\t]{0,}?[\[【][ 　\t]{0,}?.{0,}?[ 　\t]{0,}?[\]】]");
                    var matchCollapseMark = regCollapseMark.Match(regionHeaderText);
                    if (matchCollapseMark.Success)
                    {
                        // 这是改版的折叠标记，只占左上角。（2021年11月3日前后实现）
                        // 与之前“依靠标头文本折叠/展开，标头文本自身总是显示”的传统做法不同，这里的标头文本也会被隐藏。
                        // 这样比较适合制作选择题“答案&解析（详解）”。
                        var collapseMarkSpan = matchCollapseMark.Value.Trim(new char[] { ' ', '　', '\t', '[', ']', '【', '】', });
                        if (string.IsNullOrWhiteSpace(collapseMarkSpan))
                        {
                            // lineStrings[index] = $"<div id='{divID}' class='{regionClassText + divClass}'><p class='region_header' id='region_header_{regionHeaderNumber}' {headerRegionStyleText}>{FormatLineContent(ref md, regionHeaderText)}</p><div class='region_panel' id='region_panel_{regionHeaderNumber}'{regionPanelStyle}><table><tr><td>";
                            // 不再使用上面这个做法，效果不好，会出现一个普通折叠区，标头以“【】”开头。
                            collapseMarkSpan = "※";  // 直接给个默认标签即可
                        }

                        if (collapseSelf != null && ((forEpub && collapseSelf == "single-line-collapse") || collapseSelf == "single-line-expand"))
                        {
                            // 导出 epub 电子书时，总是默认显示的！
                            headerRegionStyleText = headerRegionStyleText.Substring(0, headerRegionStyleText.Length - 1) + "margin:0;border:none;display:block;'";
                        }
                        else
                        {
                            headerRegionStyleText = headerRegionStyleText.Substring(0, headerRegionStyleText.Length - 1) + "margin:0;border:none;" + regionPanelStyle.Substring(8);
                        }

                        var headerText =
                          $"<table id='r_h_{regionHeaderNumber}' class='region_header_table'>" +         // ★注意：这类自动生成的 Html 片段中，不要加不必要的 \r\n，否则会生成空 <p> </p> 片段。
                           "<tr class='region_tr'>" +
                          $"<td id='region_header_collapse_mark_{regionHeaderNumber}' class='region_collapse_mark'>" +
                          $"<p id='region_header_{regionHeaderNumber}' class='region_header_collapse_mark'>{collapseMarkSpan}</p>" +
                           "</td>" +
                           "<td class='region_header_text_td'>" +
                          $"<p id='region_header_text_{regionHeaderNumber}' class='region_header_clean' {headerRegionStyleText}{always_display_header}>{regionHeaderText.Substring(matchCollapseMark.Length).Trim()}</p>" +
                           "</td>" +
                           "</tr>" +
                           "</table>\r\n" +
                           "<script>\r\n" +
                           "$(document).ready(function(){\r\n" +
                          $"$('#region\\_header\\_collapse\\_mark\\_{regionHeaderNumber}').click(\r\n" +
                           "function(){\r\n" +
                          $@"if($('#region\\_panel\\_{regionHeaderNumber}').text().replace(/\s*/g,'').length > 0){{ $('#region\\_panel\\_{regionHeaderNumber}').toggle(); }}else{{ $('#region\\_panel\\_{regionHeaderNumber}').hide();$('#region\\_header\\_text\\_{regionHeaderNumber}').css('border-bottom', 'none'); }}" +
                          $@"if($('#region\\_header\\_text\\_{regionHeaderNumber}').attr('alwaysdisplay')!='true'){{$('#region\\_header\\_text\\_{regionHeaderNumber}').toggle();}}" +
                           "});\r\n" +
                           "})\r\n" +
                           "</script>";

                        if (region_split_area_index >= 0)
                        {
                            lineStrings[index] = $"<div id='{divID}' class='{regionClassText + divClass}'>{headerText}<div class='region_panel' id='region_panel_{regionHeaderNumber}'{regionPanelStyle}><table><tr><td id='{divID}_rarea_{region_split_area_index}' class='region_area_{region_split_area_index}'>";
                        }
                        else
                        {
                            lineStrings[index] = $"<div id='{divID}' class='{regionClassText + divClass}'>{headerText}<div class='region_panel' id='region_panel_{regionHeaderNumber}'{regionPanelStyle}><table><tr><td>";
                        }
                    }
                    else
                    {
                        // 2021年10月27日，现在，添加个新功能，允许用户在右侧添加个文本片段来实现“激活标头”的功能。
                        // 所谓“激活标头”，其实就是让标头的底色、文本色改变一下，以便在演示时吸引观众的注意力。
                        var regActiveSpan = new Regex(@"[\[【][ 　\t]{0,}?.{0,}?[ 　\t]{0,}?[\]】][ 　\t]{0,}?$");
                        var matchActiveSpan = regActiveSpan.Match(regionHeaderText);
                        if (matchActiveSpan.Success)
                        {
                            var activeSpan = matchActiveSpan.Value.Trim(new char[] { ' ', '　', '\t', '[', ']', '【', '】', });
                            if (string.IsNullOrWhiteSpace(activeSpan))
                            {
                                if (region_split_area_index >= 0)
                                {
                                    lineStrings[index] = $"<div id='{divID}' class='{regionClassText + divClass}'><p class='region_header' id='region_header_{regionHeaderNumber}' {headerRegionStyleText}>{FormatLineContent(ref md, regionHeaderText)}</p><div class='region_panel' id='region_panel_{regionHeaderNumber}'{regionPanelStyle}><table><tr><td id='{divID}_rarea_{region_split_area_index}' class='region_area_{region_split_area_index}'>";
                                }
                                else
                                {
                                    lineStrings[index] = $"<div id='{divID}' class='{regionClassText + divClass}'><p class='region_header' id='region_header_{regionHeaderNumber}' {headerRegionStyleText}>{FormatLineContent(ref md, regionHeaderText)}</p><div class='region_panel' id='region_panel_{regionHeaderNumber}'{regionPanelStyle}><table><tr><td>";
                                }
                            }
                            else
                            {
                                headerRegionStyleText = headerRegionStyleText.Substring(0, headerRegionStyleText.Length - 1) + "border-bottom:0;\'";
                                var headerText =
                                  $"<table id='r_h_{regionHeaderNumber}' class='region_header_table'>" +         // ★注意：这类自动生成的 Html 片段中，不要加不必要的 \r\n，否则会生成空 <p> </p> 片段。
                                   "<tr class='region_tr'>" +
                                   "<td class='region_header_text_td'>" +
                                  $"<p id='region_header_{regionHeaderNumber}' class='region_header' {headerRegionStyleText}>{regionHeaderText.Substring(0, regionHeaderText.Length - matchActiveSpan.Length)}</p>" +
                                   "</td>" +
                                  $"<td id='region_header_act_{regionHeaderNumber}' class='region_activator'>" +
                                  $"<p class='region_header_act_span'>{activeSpan}</p>" +
                                   "</td>" +
                                   "</tr>" +
                                   "</table>" +
                                  $"<script>var r{regionHeaderNumber}\\_actived = false;</script>\r\n" +
                                   "<script>\r\n" +
                                   "$(document).ready(function(){\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').mouseenter(\r\n" +
                                   "function(){\r\n" +
                                   "if(GetThemeName()=='light'){\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('background','#FC4659');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('color','white');\r\n" +
                                   "}else{" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('background','#33B7AA');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('color','white');\r\n" +
                                  $"r{regionHeaderNumber}\\_actived=true;\r\n" +
                                   "}\r\n" +
                                   "});\r\n" +
                                   "});\r\n" +
                                   "$(document).ready(function(){\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').mouseleave(\r\n" +
                                   "function(){\r\n" +
                                  $"if(r{regionHeaderNumber}\\_actived==false){{\r\n" +
                                   "if(GetThemeName()=='light'){\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('background','transparent');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('color','');\r\n" +
                                   "}else{\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('background','transparent');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('color','');\r\n" +
                                  $"r{regionHeaderNumber}\\_actived=true;\r\n" +
                                   "}\r\n" +
                                   "}\r\n" +
                                   "});\r\n" +
                                   "});\r\n" +
                                   "$(document).ready(function(){\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').click(\r\n" +
                                   "function(){\r\n" +
                                   "if(GetThemeName()=='light'){\r\n" +
                                  $"if(r{regionHeaderNumber}\\_actived == false){{\r\n" +
                                  //$"$('#r\\_h\\_{regionHeaderNumber}').css('background', '#FC4659').css('color', 'white');\r\n" +
                                  //$"$('#r\\_h\\_{regionHeaderNumber}').css('border-bottom', 'solid 4px #FC4659');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('background', '#FC4659');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('color', 'white');\r\n" +
                                  $"r{regionHeaderNumber}\\_actived = true;\r\n" +
                                   "}\r\n" +
                                   "else{\r\n" +
                                  //$"$('#r\\_h\\_{regionHeaderNumber}').css('background', '').css('color', '');\r\n" +
                                  //$"$('#r\\_h\\_{regionHeaderNumber}').css('border-bottom', 'solid 0px transparent');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('background', '');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('color', '');\r\n" +
                                  $"r{regionHeaderNumber}\\_actived = false;\r\n" +
                                   "}\r\n" +
                                   "}else{\r\n" +
                                  $"if(r{regionHeaderNumber}\\_actived == false){{\r\n" +
                                  //$"$('#r\\_h\\_{regionHeaderNumber}').css('background', '#33B7AA').css('color', 'white');\r\n" +
                                  //$"$('#r\\_h\\_{regionHeaderNumber}').css('border-bottom', 'solid 4px #33B7AA');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('background', '#33B7AA');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('color', 'white');\r\n" +
                                  $"r{regionHeaderNumber}\\_actived = true;\r\n" +
                                   "}\r\n" +
                                   "else{\r\n" +
                                  //$"$('#r\\_h\\_{regionHeaderNumber}').css('background', '').css('color', '');\r\n" +
                                  //$"$('#r\\_h\\_{regionHeaderNumber}').css('border-bottom', 'solid 0px transparent');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('background', '');\r\n" +
                                  $"$('#region\\_header\\_act\\_{regionHeaderNumber}').css('color', '');\r\n" +
                                  $"r{regionHeaderNumber}\\_actived = false;\r\n" +
                                   "}\r\n" +
                                   "}\r\n" +
                                   "});\r\n" +
                                   "})\r\n" +
                                   "</script>";

                                if (region_split_area_index >= 0)
                                {
                                    lineStrings[index] = $"<div id='{divID}' class='{regionClassText + divClass}'>{headerText}<div class='region_panel' id='region_panel_{regionHeaderNumber}'{regionPanelStyle}><table><tr><td id='region_panel_{regionHeaderNumber}_area_{region_split_area_index}' class='region_area_{region_split_area_index}'>";
                                }
                                else
                                {
                                    lineStrings[index] = $"<div id='{divID}' class='{regionClassText + divClass}'>{headerText}<div class='region_panel' id='region_panel_{regionHeaderNumber}'{regionPanelStyle}><table><tr><td>";
                                }
                            }
                        }
                        else
                        {
                            if (region_split_area_index >= 0)
                            {
                                lineStrings[index] = $"<div id='{divID}' class='{regionClassText + divClass}'><p class='region_header' id='region_header_{regionHeaderNumber}' {headerRegionStyleText}>{FormatLineContent(ref md, regionHeaderText)}</p><div class='region_panel' id='region_panel_{regionHeaderNumber}'{regionPanelStyle}><table><tr><td id='{divID}_rarea_{region_split_area_index}' class='region_area_{region_split_area_index}'>";
                            }
                            else
                            {
                                lineStrings[index] = $"<div id='{divID}' class='{regionClassText + divClass}'><p class='region_header' id='region_header_{regionHeaderNumber}' {headerRegionStyleText}>{FormatLineContent(ref md, regionHeaderText)}</p><div class='region_panel' id='region_panel_{regionHeaderNumber}'{regionPanelStyle}><table><tr><td>";
                            }
                        }

                        lineStrings[index] += "<script>$(document).ready(function() {" +
                                                 $@"$('#region\_header\_{regionHeaderNumber}').click(function() {{" +
                                                    $@"$('#region\_panel\_{regionHeaderNumber}').toggle();" +
                                                "});" +
                                            "}); </script><p>";
                    }
                }
                else
                {
                    regionTailNumber++;
                    Regex regexEnd = new Regex(@"^[ 　]{0,3}\}[ ]{0,}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ ]{0,})?");
                    var matchEnd = regexEnd.Match(lineStrings[index]);
                    if (matchEnd != null && matchEnd.Success)
                    {
                        var regionTailText = lineStrings[index].Substring(matchEnd.Length);
                        var tailRegionStyleText = "padding-left: 1em;padding-right: 1em;";
                        var cleanRegionTailText = regionTailText.Replace("　", " ");
                        if (cleanRegionTailText.StartsWith(";") || cleanRegionTailText.StartsWith("；"))
                        {
                            cleanRegionTailText = "";  // 允许自定义折叠区尾写些不需要输出的注释
                        }

                        if (string.IsNullOrWhiteSpace(cleanRegionTailText))
                        {
                            tailRegionStyleText += "border-top-color:transparent;border-top-width:0;border-bottom-width:0;";
                            regionTailText = "";//不再采用两个全角空格强制显示的办法，这样更节省空间。
                        }
                        else
                        {
                            var tmpTail = regionTailText.Trim(new char[] { ' ', '　', '\t', }).Replace("：", ":");
                            if (tmpTail.EndsWith(":"))
                            {
                                if (tmpTail.StartsWith(":"))
                                {
                                    // 两侧都有冒号，居中对齐
                                    // 显式指定必须居中，忽略 CSS 文件中的定义。
                                    tailRegionStyleText += "text-align:center;";
                                }
                                else
                                {
                                    // 只有右侧有冒号，右对齐
                                    // 显式指定右对齐，忽略 CSS 文件中的定义。
                                    tailRegionStyleText += "text-align:right;";
                                }
                            }
                            else
                            {
                                if (tmpTail.StartsWith(":"))
                                {
                                    // 显示指定左对齐，忽略 CSS 文件中的定义。
                                    tailRegionStyleText += "text-align:left;";
                                }
                                //else tailRegionStyleText = ""; 
                                //左右都没写冒号，则 CSS 文件决定——这是默认方式。
                            }

                            regionTailText = regionTailText.Trim(new char[] { ' ', '　', '\t', '：', ':', });
                        }

                        tailRegionStyleText = $"style='{tailRegionStyleText}'";

                        int panelNumber = 0;
                        if (panelLayerStack.Count > 0)
                        {
                            panelNumber = panelLayerStack.Pop();
                        }

                        lineStrings[index] = $"</p></td></tr></table>\n</div><p class='region_tail' id='region_tail_{panelNumber}' {tailRegionStyleText}>{FormatLineContent(ref md, regionTailText)}</p></div>";

                        lineStrings[index] += "<script>$(document).ready(function() {" +
                                         $@"$('#region\_tail\_{panelNumber}').click(function() {{" +
                                            $@"$('#region\_panel\_{panelNumber}').toggle();" +
                                        "});" +
                                    "}); </script>";
                        if (region_split_area_indexes.Count > 0)
                        {
                            region_split_area_indexes.Pop();
                        }

                        if (region_ids.Count > 0)
                        {
                            region_ids.Pop();
                        }
                    }
                    else
                    {
                        Regex regexSplitter = new Regex(@"^[ 　]{0,3}[.。]{3,}[ 　\t]{0,}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?.{0,}[.。]{3,}[ 　\t]{0,}$");
                        //这行中必须有：...region...，此行为分栏符。
                        var matchSplitter = regexSplitter.Match(lineStrings[index]);
                        if (matchSplitter != null && matchSplitter.Success)
                        {
                            if (region_split_area_indexes.Count > 0)
                            {
                                var region_split_area_index = region_split_area_indexes.Pop();
                                region_split_area_index++;
                                region_split_area_indexes.Push(region_split_area_index);

                                var divID = "";
                                if (region_ids.Count > 0)
                                {
                                    divID = region_ids.Pop();
                                }
                                if (string.IsNullOrWhiteSpace(divID) == false)
                                {
                                    lineStrings[index] = $"</td><td id='{divID}_rarea_{region_split_area_index}' class='region_area_{region_split_area_index}'>";
                                }
                                else
                                {
                                    lineStrings[index] = $"</td><td>";
                                }
                            }
                            else
                            {
                                lineStrings[index] = $"</td><td>";
                            }
                        }
                    }
                }
                #endregion

                #region 处理冒号开头的注释文本（冒号注释）
                if (lineStrings[index].StartsWith(":") || lineStrings[index].StartsWith("："))
                {
                    //以冒号开头的，作为备注。
                    //    2018年4月4日，支持类似这样的语法来设置自定义的 CSS class，
                    //    这样用户可以轻松地重写这种注释的样式。例如可以自定义一个 CSS 片段：
                    //    p.title {
                    //        font-weight: bold;
                    //        font-size: 1.5em;
                    //        text-indent: 0;
                    //        border-bottom: 0.2em solid black;
                    //        border-left: 0.5em solid black;
                    //        width: 10em;
                    //    }
                    //    span.segment {
                    //        color: red;
                    //    }
                    //    然后在 Markdown 文件中输入这样一行：
                    //    ：[title][segment:课程]标准
                    //    这样编译出来的结果应该是：
                    //    <p class="title"><span class="segment">课程</span>标准</p>
                    //    而默认情况下，编译出来应该是：
                    //    <p class='comment'>课程标准</p>
                    CompileVisualComment(lineStrings, ref md, sBuilder, ref spanNumber, index, trimChars);
                    continue;
                }
                #endregion

                #region 处理材料出处文本

                Regex regexMaterialSource = new Regex(@"^[ 　]{0,3}([>》〉]{1,1}[>》〉 　\t]{0,}){0,}——[ 　\t]{0,}《.*》.{0,}$");
                Match matchMaterialSource = regexMaterialSource.Match(lineStrings[index]);
                if (matchMaterialSource != null && matchMaterialSource.Success)
                {
                    Regex regexBlockQuoter = new Regex(@"^[ 　]{0,3}[>》〉]{1,1}[>》〉 　\t]{0,}");
                    var blockQuoterHeader = "";
                    var matchBlockQuoter = regexBlockQuoter.Match(lineStrings[index]);
                    string lineTailTextOfBlockQuoter;
                    if (matchBlockQuoter != null && matchBlockQuoter.Success)
                    {
                        blockQuoterHeader = lineStrings[index].Substring(0, matchBlockQuoter.Length)
                            .Replace("》", ">").Replace("〉", ">")
                            .Replace("　", "").Replace(" ", "").Replace("\t", "").Replace(">", "> ");
                        lineTailTextOfBlockQuoter = lineStrings[index].Substring(matchBlockQuoter.Length).Trim(new char[] { ' ', '　', '\t' });
                    }
                    else
                    {
                        lineTailTextOfBlockQuoter = lineStrings[index].Trim(new char[] { ' ', '　', '\t' });
                    }

                    Regex regexMaterialSourceStartMark = new Regex(@"——[ 　\t]{0,}《");
                    Match matchMaterialSourceStartMark = regexMaterialSource.Match(lineTailTextOfBlockQuoter);
                    if (matchMaterialSourceStartMark != null && matchMaterialSourceStartMark.Success)
                    {
                        lineTailTextOfBlockQuoter = regexMaterialSourceStartMark.Replace(lineTailTextOfBlockQuoter, "——《");
                    }

                    sBuilder.Append($"{blockQuoterHeader}<p class='ex_m_cc'>{FormatLineContent(ref md, lineTailTextOfBlockQuoter)}</p>\r\n");
                    sBuilder.Append("\r\n");
                    continue;
                }

                Regex regexMaterialSource2 = new Regex(@"^[ 　]{0,3}([>》〉]{1,1}[>》〉 　\t]{0,}){0,}→→[ 　\t]{0,}.*$");
                Match matchMaterialSource2 = regexMaterialSource2.Match(lineStrings[index]);
                if (matchMaterialSource2 != null && matchMaterialSource2.Success)
                {
                    Regex regexBlockQuoter2 = new Regex(@"^[ 　]{0,3}[>》〉]{1,1}[>》〉 　\t]{0,}");
                    var blockQuoterHeader2 = "";
                    var matchBlockQuoter2 = regexBlockQuoter2.Match(lineStrings[index]);
                    string lineTailTextOfBlockQuoter2;
                    if (matchBlockQuoter2 != null && matchBlockQuoter2.Success)
                    {
                        blockQuoterHeader2 = lineStrings[index].Substring(0, matchBlockQuoter2.Length);
                        lineTailTextOfBlockQuoter2 = lineStrings[index].Substring(matchBlockQuoter2.Length).Trim(new char[] { ' ', '　', '\t' });
                    }
                    else
                    {
                        lineTailTextOfBlockQuoter2 = lineStrings[index].Trim(new char[] { ' ', '　', '\t' });
                    }

                    Regex regexMaterialSourceStartMark2 = new Regex(@"→→[ 　\t]{0,}");
                    Match matchMaterialSourceStartMark2 = regexMaterialSource2.Match(lineTailTextOfBlockQuoter2);
                    if (matchMaterialSourceStartMark2 != null && matchMaterialSourceStartMark2.Success)
                    {
                        lineTailTextOfBlockQuoter2 = regexMaterialSourceStartMark2.Replace(lineTailTextOfBlockQuoter2, "——");
                    }

                    sBuilder.Append($"{blockQuoterHeader2}<p class='ex_m_cc'>{FormatLineContent(ref md, lineTailTextOfBlockQuoter2)}</p>\r\n");
                    sBuilder.Append("\r\n");
                    continue;
                }

                #endregion

                #region 处理任务列表项
                if (lineStrings[index].StartsWith("[-]") || lineStrings[index].StartsWith("[－]"))
                {
                    //以[-]开头的，作为尚未开始的任务列表项目。
                    sBuilder.Append($"<p class=\"task\"><span class='task_u'>[-]</span> <span class='task_up'>{FormatLineContent(ref md, lineStrings[index].Substring(4))}</span></p>");
                    sBuilder.Append("\r\n");
                    continue;
                }

                if (lineStrings[index].StartsWith("[%]"))
                {
                    //以[%]开头的，作为正在进行的任务列表项目。
                    sBuilder.Append($"<p class=\"task\"><span class='task_p'>[%]</span> <span class='task_pp'>{FormatLineContent(ref md, lineStrings[index].Substring(4))}</span></p>");
                    sBuilder.Append("\r\n");
                    continue;
                }

                if (lineStrings[index].StartsWith("[+]"))
                {
                    //以[+]开头的，作为已完成任务列表项目。
                    sBuilder.Append($"<p class=\"task\"><span class='task_f'>[+]</span> <span class='task_fp'><s>{FormatLineContent(ref md, lineStrings[index].Substring(4))}</s></span></p>");
                    sBuilder.Append("\r\n");
                    continue;
                }

                if (lineStrings[index].StartsWith("[#]"))
                {
                    //以[#]开头的，作为已废弃任务列表项目。
                    sBuilder.Append($"<p class=\"task\"><span class='task_a'>[#]</span> <span class='task_ap'><s>{FormatLineContent(ref md, lineStrings[index].Substring(4))}</s></span></p>");
                    sBuilder.Append("\r\n");
                    continue;
                }

                if (IsDateLine(lineStrings[index]))
                {
                    var className = "date";
                    var indexStatus = -1;
                    indexStatus = lineStrings[index].IndexOf("][-]");
                    if (indexStatus >= 0) className = "date_u";
                    else
                    {
                        indexStatus = lineStrings[index].IndexOf("][%]");
                        if (indexStatus >= 0)
                        {
                            className = "date_p";
                        }
                        else
                        {
                            indexStatus = lineStrings[index].IndexOf("][+]");
                            if (indexStatus >= 0)
                            {
                                className = "date_f";
                            }
                            else
                            {
                                indexStatus = lineStrings[index].IndexOf("][#]");
                                if (indexStatus >= 0)
                                {
                                    className = "date_a";
                                }
                            }
                        }
                    }

                    if (indexStatus >= 0)
                    {
                        sBuilder.Append("<p><span class='" + className + $"'>{FormatLineContent(ref md, lineStrings[index].Substring(0, indexStatus + 4))}</span>{lineStrings[index].Substring(indexStatus + 5)}</p>");
                        sBuilder.Append("\r\n");
                        continue;
                    }

                    var indexOfSquareBracket = lineStrings[index].IndexOf("]");
                    if (indexOfSquareBracket < 0)
                    {
                        sBuilder.Append(lineStrings[index]);
                        sBuilder.Append("\r\n");
                        continue;
                    }
                    else
                    {
                        sBuilder.Append($"<p><span class='date'>{FormatLineContent(ref md, lineStrings[index].Substring(0, indexOfSquareBracket + 1))}</span>{lineStrings[index].Substring(indexOfSquareBracket + 1)}</p>");
                        sBuilder.Append("\r\n");
                        continue;
                    }
                }
                #endregion

                #region 处理应放大显示的文本行

                Regex regexMultyText = new Regex(@"^[ 　]{0,3}<[1234567890１２３４５６７８９０]{1,}([.。][1234567890１２３４５６７８９０]{1,}){0,}>");
                var matchMultyText = regexMultyText.Match(lineStrings[index]);
                if (matchMultyText.Success)
                {
                    double multi;
                    if (double.TryParse(matchMultyText.Value.Substring(1, matchMultyText.Length - 2), out multi) == false)
                    {
                        multi = 1;
                    }

                    if (multi < 1) multi = 1;
                    if (multi > 10) multi = 10;

                    var multiContent = lineStrings[index].Substring(matchMultyText.Length).TrimStart();

                    var regexIdOrClassText = new Regex(@"[\(（][ 　\t]*[@&][a-zA-Z][a-zA-Z0-9]{0,}[ 　\t]*[\)）]");
                    var matchIdOrClassText = regexIdOrClassText.Match(multiContent);
                    string line;
                    if (matchIdOrClassText.Success)
                    {
                        multiContent = multiContent.Substring(matchIdOrClassText.Length).Trim();

                        var idOrClass = matchIdOrClassText.Value.Trim(new char[] { ' ', '　', '\t', '(', '（', ')', '）' });

                        if (idOrClass.StartsWith("@"))
                        {
                            if (string.IsNullOrWhiteSpace(idOrClass))
                            {
                                line = $"<div class=\"multiple-text\"><p class=\"muilti_para\" style=\"font-size:{multi}em;line-height:1.2em;\">{FormatLineContent(ref md, multiContent)}</p></div>";
                            }
                            else
                            {
                                var idMark = idOrClass.Trim(new char[] { ' ', '　', '\t', '(', '（', '@', '&', ')', '）' });
                                line = $"<div id=\"{idMark}\" class=\"multiple-text\"><p class=\"muilti_para\" style=\"font-size:{multi}em;line-height:1.2em;\">{FormatLineContent(ref md, multiContent)}</p></div>";
                            }
                        }
                        else if (idOrClass.StartsWith("&"))
                        {
                            if (string.IsNullOrWhiteSpace(idOrClass))
                            {
                                line = $"<div class=\"multiple-text\"><p class=\"muilti_para\" style=\"font-size:{multi}em;line-height:1.2em;\">{FormatLineContent(ref md, multiContent)}</p></div>";
                            }
                            else
                            {
                                var classMark = idOrClass.Trim(new char[] { ' ', '　', '\t', '(', '（', '@', '&', ')', '）' });
                                line = $"<div class=\"multiple-text {classMark}\"><p class=\"muilti_para\" style=\"font-size:{multi}em;line-height:1.2em;\">{FormatLineContent(ref md, multiContent)}</p></div>";
                            }
                        }
                        else
                        {
                            line = $"<div class=\"multiple-text\"><p class=\"muilti_para\" style=\"font-size:{multi}em;line-height:1.2em;\">{FormatLineContent(ref md, multiContent)}</p></div>";
                        }
                    }
                    else
                    {
                        line = $"<div class=\"multiple-text\"><p class=\"muilti_para\" style=\"font-size:{multi}em;line-height:1.2em;\">{FormatLineContent(ref md, multiContent)}</p></div>";
                    }

                    sBuilder.Append(line);
                    sBuilder.Append("\r\n");
                    continue;
                }

                #endregion

                #region 处理由双冒号截断的行头片段（通常用以加粗行首文本）
                if (Globals.MainWindow.SupportDoubleColon)
                {
                    Regex regexBoldHeader = new Regex(@"^[ 　]{0,3}[\u4e00-\u9fa5A-Za-zａ-ｚＡ-Ｚ'""“‘’”0-9０-９\(\)《〈〉》、．·—\-][\u4e00-\u9fa5A-Za-zａ-ｚＡ-Ｚ'""“‘’”0-9０-９ 　\(\)《〈〉》、．·—\-]{0,}[:：]{2,}[ 　\t]{0,}");
                    var matchBoldHeader = regexBoldHeader.Match(lineStrings[index]);
                    if (matchBoldHeader.Success)
                    {
                        var colonMark = "：";
                        var tail = lineStrings[index].Substring(matchBoldHeader.Length);
                        if (string.IsNullOrWhiteSpace(tail)) colonMark = "";  // 当双冒号后没有内容时，只加粗到行首的文本。

                        if (matchBoldHeader.Value.Trim(new char[] { ' ', '　', '\t', ':', '：', }) == "举个栗子")
                        {
                            if (forEpub)
                            {
                                sBuilder.Append("<div class='para_header_div_example'>");
                                sBuilder.Append("<p class='para_header_example'><img class='para_header_icon_example' src='../Images~/_example.png' is_quick_image='true'/>");
                                sBuilder.Append($"<Span class='para_header_span_example'>举个例子：</Span>{tail}</p>");  // 电子书不适合加空格
                                sBuilder.Append("</div>\r\n");
                            }
                            else
                            {
                                sBuilder.Append("<div class='para_header_div_example'>");
                                sBuilder.Append($"<p class='para_header_example'> <img class='para_header_icon_example' src='{toWorkspacePrefix + (forEpub ? "../Images~/" : "Images~/") + "_example.png"}' is_quick_image='true'/>");
                                sBuilder.Append($"<Span class='para_header_span_example'>　举个例子：</Span>{tail}</p>");
                                sBuilder.Append("</div>\r\n");

                                //sBuilder.Append("<div class=\"para_header_div_example\">");
                                //sBuilder.Append("<p class=\"para_header_with_icon para_header_example\"><Span class='para_header'>举个 </Span><span class=\"para_header_icon_span para_header_icon_example\"></span>");
                                //sBuilder.Append($"<Span> ：</Span>{tail}</p>");
                                //sBuilder.Append("</div>\r\n");
                                //上面这个效果是“举例[图标]：”

                                //下面这个是图标在前，后面跟“举个例子：”
                                //sBuilder.Append("<div class=\"para_header_div_example\">");
                                //sBuilder.Append("<p class=\"para_header_example\"><span class=\"para_header_icon_example\"></span>");
                                //sBuilder.Append($"<Span> </Span><Span class='para_header'>举个例子：</Span>{tail}</p>");
                                //sBuilder.Append("</div>\r\n");
                            }
                        }
                        else
                        {
                            sBuilder.Append("<Span class='para_header'>" +
                                matchBoldHeader.Value.Trim(new char[] { ' ', '　', '\t', ':', '：', }) + colonMark + "</Span>" + FormatLineContent(ref md, tail.Trim(trimChars)));
                            sBuilder.Append("\r\n");
                            sBuilder.Append("\r\n");
                        }
                        continue;
                    }
                }
                #endregion 处理由双冒号截断的行头片段

                #region 处理由双逗号截断的行头片段（通常用以加粗行首文本）
                if (Globals.MainWindow.SupportDoubleComma)
                {
                    Regex regexBoldHeaderByComma = new Regex(@"^[ 　]{0,3}[\u4e00-\u9fa5A-Za-zａ-ｚＡ-Ｚ'""“‘’”0-9０-９\(\)《〈〉》、．·—\-][\u4e00-\u9fa5A-Za-zａ-ｚＡ-Ｚ'""“‘’”0-9０-９ 　\(\)《〈〉》、．·—\-]{0,}[,，]{2,}[ 　\t]{0,}");
                    var matchBoldHeaderByComma = regexBoldHeaderByComma.Match(lineStrings[index]);
                    if (matchBoldHeaderByComma.Success)
                    {
                        var commaMark = "，";
                        var tail = lineStrings[index].Substring(matchBoldHeaderByComma.Length);
                        if (string.IsNullOrWhiteSpace(tail)) commaMark = "";  // 当双逗号后没有内容时，只加粗到行首的文本。

                        sBuilder.Append("<Span class='para_header_comma'>" +
                            matchBoldHeaderByComma.Value.Trim(new char[] { ' ', '　', '\t', ':', ',', '，' }) + commaMark + "</Span>" + FormatLineContent(ref md, tail.Trim(trimChars)));
                        sBuilder.Append("\r\n");
                        sBuilder.Append("\r\n");
                        continue;
                    }
                }
                #endregion 处理由双逗号截断的行头片段

                #region 处理由双句号截断的行头片段（通常用以加粗行首文本）
                if (Globals.MainWindow.SupportDoublePeriod)
                {
                    //Regex regexBoldHeaderByPeriod = new Regex(@"^[ 　]{0,3}[^。?？!！]{0,}。{2,}[ 　\t]{0,}");
                    Regex regexBoldHeaderByPeriod = new Regex(@"^[ 　]{0,3}[^ 　\t].{1,}。{2,}[ 　\t]{0,}");
                    var matchBoldHeaderByPeriod = regexBoldHeaderByPeriod.Match(lineStrings[index]);
                    if (matchBoldHeaderByPeriod.Success)
                    {
                        //var periodMark = "。";
                        //var tail = lineStrings[index].Substring(matchBoldHeaderByPeriod.Length);
                        //// if (string.IsNullOrWhiteSpace(tail)) periodMark = "";  // 当双句号后没有内容时，加粗到行首的文本，且应保留一个句号。

                        //sBuilder.Append("<Span class='para_header_period'>" +
                        //    matchBoldHeaderByPeriod.Value.Trim(new char[] { ' ', '　', '\t', ':', '。', }) + periodMark + "</Span>" + FormatLineContent(ref md, tail.Trim(trimChars)));
                        //sBuilder.Append("\r\n");
                        //sBuilder.Append("\r\n");
                        //continue;

                        var tail = lineStrings[index].Substring(matchBoldHeaderByPeriod.Length);
                        tail = tail.Trim(trimChars);
                        bool noTail = string.IsNullOrWhiteSpace(tail);

                        if (noTail)
                        {
                            if (forEpub)
                            {
                                sBuilder.Append("<div class='para_header_div_paragraph'>");
                                sBuilder.Append("<p class='para_header_paragraph'><img class='para_header_icon_paragraph' src='../Images~/_paragraph.png' is_quick_image='true'/>");
                                sBuilder.Append($"<Span class='para_header_span_paragraph'>" + // 注意：标题文本前面加个空格和图标隔开比较好，但电子书不适合加空格。
                                    matchBoldHeaderByPeriod.Value.Trim(new char[] { ' ', '　', '\t', ':', '。', }) + "。</Span></p>");
                                sBuilder.Append("</div>\r\n");
                            }
                            else
                            {
                                sBuilder.Append("<div class='para_header_div_paragraph'>");
                                sBuilder.Append($"<p class='para_header_paragraph'><img class='para_header_icon_paragraph' src='{toWorkspacePrefix + (forEpub ? "../Images~/" : "Images~/") + "_paragraph.png"}' is_quick_image='true'/>");
                                sBuilder.Append($"<Span class='para_header_span_paragraph'>　" + // 注意：标题文本前面加个空格和图标隔开比较好。
                                    matchBoldHeaderByPeriod.Value.Trim(new char[] { ' ', '　', '\t', ':', '。', }) + "。</Span></p>");
                                sBuilder.Append("</div>\r\n");
                            }
                        }
                        else
                        {
                            tail = Regex.Replace(tail, @"<br[ 　\t]{0,}?/?>", "</p><p class='para_header_tail_paragraph'>", RegexOptions.IgnoreCase);
                            if (forEpub)
                            {
                                sBuilder.Append("<div class='para_header_divbox_paragraph'>");
                                sBuilder.Append("<p class='para_header_paragraph'><img class='para_header_icon_paragraph' src='../Images~/_paragraph.png' is_quick_image='true'/>");
                                sBuilder.Append(/*<span>　</span>*/$"<Span class='para_header_span_paragraph_withtail'>" + // 注意：标题文本前面加个空格和图标隔开比较好，但电子书不适合加空格。
                                    matchBoldHeaderByPeriod.Value.Trim(new char[] { ' ', '　', '\t', ':', '。', }) + "。</Span></p><p class='para_header_tail_paragraph'>" + FormatLineContent(ref md, tail, true) + "</p>");
                                sBuilder.Append("</div>\r\n");
                            }
                            else
                            {
                                dbl_mark_index++;

                                sBuilder.Append("<div class='para_header_divbox_paragraph'>");
                                sBuilder.Append($"<p id='dbl_mark_header_{dbl_mark_index}' class='para_header_paragraph'><img class='para_header_icon_paragraph' src='{toWorkspacePrefix + (forEpub ? "../Images~/" : "Images~/") + "_paragraph.png"}' is_quick_image='true'/>");
                                sBuilder.Append($"<span>　</span><Span class='para_header_span_paragraph_withtail'>" + // 注意：标题文本前面加个空格和图标隔开比较好。
                                    matchBoldHeaderByPeriod.Value.Trim(new char[] { ' ', '　', '\t', ':', '。', }) + $"。</Span></p>" +
                                    $"<div id='dbl_mark_para_{dbl_mark_index}'><p class='para_header_tail_paragraph'>" + FormatLineContent(ref md, tail, true) + "</p></div>");
                                sBuilder.Append("</div>\r\n");
                            }
                        }
                        continue;
                    }
                }
                #endregion 处理由双句号截断的行头片段

                #region 处理由多个问号截断的行头片段（通常用以加粗行首文本）
                if (Globals.MainWindow.SupportMultiQuestion)
                {
                    //Regex regexBoldHeaderByQuestion = new Regex(@"^[ 　]{0,3}[^。?？!！]{0,}[?？]{2,}[ 　\t]{0,}");
                    Regex regexBoldHeaderByQuestion = new Regex(@"^[ 　]{0,3}[^ 　\t].{1,}[?？]{2,}[ 　\t]{0,}");
                    var matchBoldHeaderByQuestion = regexBoldHeaderByQuestion.Match(lineStrings[index]);
                    if (matchBoldHeaderByQuestion.Success)
                    {
                        var questionMark = "？";
                        var regexOfQuestion = new Regex(@"[?？]{1,}$");
                        var matchOfQuestion = regexOfQuestion.Match(matchBoldHeaderByQuestion.Value);
                        if (matchOfQuestion.Success)
                        {
                            if (matchOfQuestion.Length > 3)
                            {
                                questionMark = "？？？";
                            }
                            else if (matchOfQuestion.Length == 3)
                            {
                                questionMark = "？？";
                            }
                        }
                        var tail = lineStrings[index].Substring(matchBoldHeaderByQuestion.Length);
                        // if (string.IsNullOrWhiteSpace(tail)) questionMark = "";  // 当双句号后没有内容时，加粗到行首的文本，保留1-3个问号。

                        // 这个版本不好，不能实现文本行与行内图标垂直对齐
                        //sBuilder.Append("<img class=\"img_question\" src=\"" + toWorkspacePrefix + (forEpub ? "../Images~/" : "Images~/") + "_question.png" + "\" is_quick_image=\"true\"/>");  // 添加对快捷图标的引用。
                        //sBuilder.Append("<Span class='para_header_question'>" +
                        //    matchBoldHeaderByQuestion.Value.Trim(new char[] { ' ', '　', '\t', ':', '?', '？', }) + questionMark + "</Span>" + tail.Trim(trimChars));
                        //sBuilder.Append("\r\n");
                        //sBuilder.Append("\r\n");

                        tail = tail.Trim(trimChars);
                        bool noTail = string.IsNullOrWhiteSpace(tail);

                        if (noTail)
                        {
                            if (forEpub)
                            {
                                sBuilder.Append("<div class='para_header_div_question'>");
                                sBuilder.Append("<p class='para_header_question'><img class='para_header_icon_question' src='../Images~/_question.png' is_quick_image='true'/>");
                                sBuilder.Append($"<Span class='para_header_span_question'>" + // 注意：标题文本前面加个空格和图标隔开比较好，但电子书不适合加空格。
                                    matchBoldHeaderByQuestion.Value.Trim(new char[] { ' ', '　', '\t', ':', '?', '？', }) + questionMark + "</Span></p>");
                                sBuilder.Append("</div>\r\n");
                            }
                            else
                            {
                                sBuilder.Append("<div class='para_header_div_question'>");
                                sBuilder.Append($"<p class='para_header_question'><img class='para_header_icon_question' src='{toWorkspacePrefix + (forEpub ? "../Images~/" : "Images~/") + "_question.png"}' is_quick_image='true'/>");
                                sBuilder.Append($"<Span class='para_header_span_question'>　" + // 注意：标题文本前面加个空格和图标隔开比较好。
                                    matchBoldHeaderByQuestion.Value.Trim(new char[] { ' ', '　', '\t', ':', '?', '？', }) + questionMark + "</Span></p>");
                                sBuilder.Append("</div>\r\n");
                            }
                        }
                        else
                        {
                            tail = Regex.Replace(tail, @"<br[ 　\t]{0,}?/?>", "</p><p class='para_header_tail_question'>", RegexOptions.IgnoreCase);
                            if (forEpub)
                            {
                                sBuilder.Append("<div class='para_header_divbox_question'>");
                                sBuilder.Append("<p class='para_header_question'><img class='para_header_icon_question' src='../Images~/_question.png' is_quick_image='true'/>");
                                sBuilder.Append(/*<span>　</span>*/$"<Span class='para_header_span_question_withtail'>" + // 注意：标题文本前面加个空格和图标隔开比较好。
                                    matchBoldHeaderByQuestion.Value.Trim(new char[] { ' ', '　', '\t', ':', '?', '？', }) + questionMark + "</Span></p><p class='para_header_tail_question'>" + FormatLineContent(ref md, tail, true) + "</p>");
                                sBuilder.Append("</div>\r\n");
                            }
                            else
                            {
                                dbl_mark_index++;

                                sBuilder.Append("<div class='para_header_divbox_question'>");

                                // 只有问号才应该默认折叠！
                                sBuilder.Append($"<p id='dbl_mark_header_{dbl_mark_index}' class='para_header_question'><img class='para_header_icon_question' src='{toWorkspacePrefix + (forEpub ? "../Images~/" : "Images~/") + "_question.png"}' is_quick_image='true'/>");
                                sBuilder.Append($"<span>　</span><Span class='para_header_span_question_withtail'>" +  // 注意：标题文本前面加个空格和图标隔开比较好。
                                    matchBoldHeaderByQuestion.Value.Trim(new char[] { ' ', '　', '\t', ':', '?', '？', }) + questionMark + $"</Span></p>" +
                                    $"<div id='dbl_mark_para_{dbl_mark_index}' style='display:none;'><p class='para_header_tail_question'>" + FormatLineContent(ref md, tail, true) + "</p></div>");
                                sBuilder.Append("</div>\r\n");
                            }
                        }
                        continue;
                    }
                }
                #endregion 处理由多个问号截断的行头片段

                #region 处理由多个惊叹号截断的行头片段（通常用以加粗行首文本）
                if (Globals.MainWindow.SupportMultiExclamation)
                {
                    //Regex regexBoldHeaderByExclamation = new Regex(@"^[ 　]{0,3}(注意[：:])?[^?？!！。]{0,}[!！]{2,}[ 　\t]{0,}");
                    Regex regexBoldHeaderByExclamation = new Regex(@"(^[ 　]{0,3}注意[：:].{0,}[!！]{2,}[ 　\t]{0,})|(^[ 　]{0,3}[^ 　\t].{0,}[!！]{2,}[ 　\t]{0,})");
                    var matchBoldHeaderByExclamation = regexBoldHeaderByExclamation.Match(lineStrings[index]);
                    if (matchBoldHeaderByExclamation.Success)
                    {
                        var exclamationMark = "！";
                        var regexOfExclamation = new Regex(@"[!！]{1,}$");
                        var matchOfExclamation = regexOfExclamation.Match(matchBoldHeaderByExclamation.Value);
                        if (matchOfExclamation.Success)
                        {
                            if (matchOfExclamation.Length > 3)
                            {
                                exclamationMark = "！！！";
                            }
                            else if (matchOfExclamation.Length == 3)
                            {
                                exclamationMark = "！！";
                            }
                        }
                        var tail = lineStrings[index].Substring(matchBoldHeaderByExclamation.Length);
                        // if (string.IsNullOrWhiteSpace(tail)) exclamationMark = "";  // 当双句号后没有内容时，加粗到行首的文本，且保留 1-3 个惊叹号。

                        // 这个版本不好，不能实现文本行与行内图标垂直对齐
                        //sBuilder.Append("<img class=\"img_exclamation\" src=\"" + toWorkspacePrefix + (forEpub ? "../Images~/" : "Images~/") + "_exclamation.png" + "\" is_quick_image=\"true\"/>");  // 添加对快捷图标的引用。
                        //sBuilder.Append("<Span class='para_header_exclamation'>" +
                        //    matchBoldHeaderByExclamation.Value.Trim(new char[] { ' ', '　', '\t', ':', '!', '！', }) + exclamationMark + "</Span>" + tail.Trim(trimChars));
                        //sBuilder.Append("\r\n");
                        //sBuilder.Append("\r\n");

                        tail = tail.Trim(trimChars);
                        bool noTail = string.IsNullOrWhiteSpace(tail);

                        if (noTail)
                        {
                            if (forEpub)
                            {
                                sBuilder.Append("<div class='para_header_div_exclamation'>");
                                sBuilder.Append("<p class='para_header_exclamation'><img class='para_header_icon_exclamation' src='../Images~/_exclamation.png' is_quick_image='true'/>");
                                sBuilder.Append($"<Span class='para_header_span_exclamation'>" + // 注意：标题文本前面加个空格和图标隔开比较好，但电子书不适合加空格。
                                    matchBoldHeaderByExclamation.Value.Trim(new char[] { ' ', '　', '\t', ':', '!', '！', }) + exclamationMark + "</Span></p>");
                                sBuilder.Append("</div>\r\n");
                            }
                            else
                            {
                                sBuilder.Append("<div class='para_header_div_exclamation'>");
                                sBuilder.Append($"<p class='para_header_exclamation'><img class='para_header_icon_exclamation' src='{toWorkspacePrefix + (forEpub ? "../Images~/" : "Images~/") + "_exclamation.png"}' is_quick_image='true'/>");
                                sBuilder.Append($"<Span class='para_header_span_exclamation'>　" + // 注意：标题文本前面加个空格和图标隔开比较好。
                                    matchBoldHeaderByExclamation.Value.Trim(new char[] { ' ', '　', '\t', ':', '!', '！', }) + exclamationMark + "</Span></p>");
                                sBuilder.Append("</div>\r\n");
                            }
                        }
                        else
                        {
                            tail = Regex.Replace(tail, @"<br[ 　\t]{0,}?/?>", "</p><p class='para_header_tail_exclamation'>", RegexOptions.IgnoreCase);
                            if (forEpub)
                            {
                                sBuilder.Append("<div class='para_header_divbox_exclamation'>");
                                sBuilder.Append("<p class='para_header_exclamation'><img class='para_header_icon_exclamation' src='../Images~/_exclamation.png' is_quick_image='true'/>");
                                sBuilder.Append(/*<span>　</span>*/$"<Span class='para_header_span_exclamation_withtail'>" + // 注意：标题文本前面加个空格和图标隔开比较好，但电子书不适合加空格。
                                    matchBoldHeaderByExclamation.Value.Trim(new char[] { ' ', '　', '\t', ':', '!', '！', }) + exclamationMark + "</Span></p><p class='para_header_tail_exclamation'>" + FormatLineContent(ref md, tail, true) + "</p>");
                                sBuilder.Append("</div>\r\n");
                            }
                            else
                            {
                                dbl_mark_index++;

                                sBuilder.Append("<div class='para_header_divbox_exclamation'>");
                                sBuilder.Append($"<p id='dbl_mark_header_{dbl_mark_index}' class='para_header_exclamation'><img class='para_header_icon_exclamation' src='{toWorkspacePrefix + (forEpub ? "../Images~/" : "Images~/") + "_exclamation.png"}' is_quick_image='true'/>");
                                sBuilder.Append($"<span>　</span><Span class='para_header_span_exclamation_withtail'>" + // 注意：标题文本前面加个空格和图标隔开比较好。
                                    matchBoldHeaderByExclamation.Value.Trim(new char[] { ' ', '　', '\t', ':', '!', '！', }) + exclamationMark + $"</Span></p>" +
                                    $"<div id='dbl_mark_para_{dbl_mark_index}'><p class='para_header_tail_exclamation'>" + FormatLineContent(ref md, tail, true) + "</p></div>");
                                sBuilder.Append("</div>\r\n");
                            }
                        }
                        continue;
                    }
                }
                #endregion 处理由多个惊叹号截断的行头片段

                #region 处理步骤标记
                Regex regexStepLine = new Regex(@"^[ 　]{0,3}\[[ 　]{0,}(([sSｓＳ]([bBｂＢ]|[\-－—]|[tTｔＴ]|[pPｐＰ]|[eEｅＥ]))|([sSｓＳ]([tTｔＴ][eEｅＥ][pPｐＰ]){0,1}[ 　]{0,}(([bBｂＢ]([eEｅＥ][gGｇＧ][iIｉＩ][nNｎＮ]){0,1})|([eEｅＥ]([nNｎＮ][dDｄＤ]){0,1})){0,1}))\]");
                var matchStepLine = regexStepLine.Match(lineStrings[index]);
                if (matchStepLine.Success)
                {
                    var tail = lineStrings[index].Substring(matchStepLine.Length);

                    var stepMarkText = matchStepLine.Value.ToLower();
                    string line;
                    if (stepMarkText.EndsWith("begin]") || stepMarkText == "[sb]")
                    {
                        stepBaseCount = 0;            //重置为1；
                    }

                    line = $"<p class=\"step-para\"><span class=\"step-mark\">Step {++stepBaseCount}</span> {FormatLineContent(ref md, tail.Trim(new char[] { ' ', '　' }))}</p>";

                    //不必考虑全角字符，因为之前格式化时会将全角格式化为半角并补全。
                    if (stepMarkText.EndsWith("end]") || stepMarkText == "[se]" /*|| stepMarkText.EndsWith("e]")*/) stepBaseCount = 0;//归零。

                    sBuilder.Append(line);
                    sBuilder.Append("\r\n");
                    continue;
                }
                #endregion 处理步骤标记

                #region 处理以替换前导字符开头的文本行

                // 前导字符通常是 $
                // 如果保留前导字符，编译出来的 Html 文件会极其丑陋；
                // 此时可以考虑先把前导字符 $ 替换为 &zwnj; （即零宽不连字符，参见前面的注释）
                // 还是直接替换成 &nbsp; 好。
                var replaceLeaderChar = Globals.MainWindow.ReplaceLeaderChar;
                var regReplaceLeader = new Regex("^[" + replaceLeaderChar + "][ 　]{0,}");
                var matchReplaceLeader = regReplaceLeader.Match(lineStrings[index]);
                if (matchReplaceLeader.Success)
                {
                    string replaceLeaderText = matchReplaceLeader.Value;
                    for (int a = 0; a < replaceLeaderText.Length; a++)
                    {
                        if (replaceLeaderText[a] == '　')    //全角空格原样输出即可。
                        {
                            sBuilder.Append("　");
                        }
                        else sBuilder.Append("&nbsp;");
                    }
                    sBuilder.Append(lineStrings[index].Substring(matchReplaceLeader.Length));
                    sBuilder.Append("\r\n\r\n");
                    continue;
                }

                #endregion

                // ==================================================================================
                // 三尖括号左右对齐的优先级应该是最低的，但不应放在表格后。
                // ==================================================================================
                #region 不应抢先处理 >>> 结尾的文本行和 <<< 开头的文本行

                if (lineStrings[index].StartsWith("    ") == false && lineStrings[index].StartsWith("\t") == false)  // 代码块中无效
                {
                    if (IsRegionStart(lineStrings[index]) || IsRegionEnd(lineStrings[index]) ||
                        IsSquareStart(lineStrings[index]) || IsSquareEnd(lineStrings[index]))
                    {
                        // if (IsCommentLine(lineStrings[index])) continue;  // 这个不需要。
                        sBuilder.Append(lineStrings[index] + separatorText[0]);
                        sBuilder.Append("\r\n");   // 一定要多加个空行，否则元素之间会混淆。例如跟在普通文本段落后面的树型文字表。
                        continue;
                    }

                    var title_text = lineStrings[index].Trim();
                    if (title_text.StartsWith("<<<"))
                    {
                        var trimAlignChars = new char[] { ' ', '　', '\t', '<', '>' };
                        if (title_text.EndsWith(">>>"))
                        {
                            // 居中
                            sBuilder.Append($"<p class=\"align_text_center\">{FormatLineContent(ref md, title_text.Trim(trimAlignChars))}</p>");
                            sBuilder.Append("\r\n");
                            continue;
                        }
                        else
                        {
                            // 居左
                            sBuilder.Append($"<p class=\"align_text_left\">{FormatLineContent(ref md, title_text.Trim(trimAlignChars))}</p>");
                            sBuilder.Append("\r\n");
                            continue;
                        }
                    }
                    else
                    {
                        var trimAlignChars = new char[] { ' ', '　', '\t', '<', '>' };
                        if (title_text.EndsWith(">>>"))
                        {
                            // 居右
                            sBuilder.Append($"<p class=\"align_text_right\">{FormatLineContent(ref md, title_text.Trim(trimAlignChars))}</p>");
                            sBuilder.Append("\r\n");
                            continue;
                        }
                        // 左右均无此标记，直接视为普通文本，继续处理。
                    }
                }

                #endregion

                // ==================================================================================
                // 注意，通常不应把其它项目的处理放在表格的处理之后，因为会无效！！！！！！！！！！！
                #region 处理表格信息
                TableLinesInfo tableLinesInfo = InWhitchTable(index, tableSourcesList);

                if (null == tableLinesInfo)
                {
                    sBuilder.Append(lineStrings[index] + separatorText[0]);
                    sBuilder.Append("\r\n");   // 一定要多加个空行，否则元素之间会混淆。例如跟在普通文本段落后面的树型文字表。
                    continue;
                }

                if (tableLinesInfo.IsUsed) continue;

                sBuilder.Append(tableLinesInfo.TableHtmlText);
                tableLinesInfo.IsUsed = true;
                #endregion
                //★★★★★★注意：不能将其它文本的处理放在表格信息之后，因为可能不起作用。
                // ==================================================================================

            }

            stepBaseCount = 0;//整个处理结束后，必须归零，以便下次使用。

            return sBuilder.ToString();
        }

        /// <summary>
        /// 将连续出现的类似下面这样的行视为布局行，转换成一个 Table。
        /// 30% xxx
        /// 250px xxx
        /// </summary>
        /// <param name="markdownText"></param>
        /// <returns></returns>
        private static string LayoutLineCompile(string markdownText)
        {
            if (string.IsNullOrWhiteSpace(markdownText)) return "";

            var lines = markdownText.Split(new string[] { "\r\n" }, StringSplitOptions.None);
            var layoutTableList = new List<LayoutTableLine>();

            var sb = new StringBuilder();

            foreach (var line in lines)
            {
                if (IsLayoutLine(line, out string width, out string content, out string id))
                {
                    layoutTableList.Add(new LayoutTableLine() { WidthText = width, TdContent = content, TdId = id });
                }
                else
                {
                    if (layoutTableList.Count > 0 && string.IsNullOrWhiteSpace(line) == false && IsCommentLine(line) == false)
                    {
                        var sbTable = new StringBuilder();
                        // 转换为table，为防止其中的图像链接等不起作用，还是先用注释的方式，然后在 AppendCompile 中继续处理。
                        sbTable.Append("<!--table class='line_layout_table'><tr class='line_layout_tr'>-->\r\n");
                        foreach (var tline in layoutTableList)
                        {
                            var tdHeader = "<!--td class='line_layout_td' style='width:" + tline.WidthText + ";' id='" + tline.TdId + "'==tdId-->\r\n";
                            var tdTail = "\r\n<!--/td class='line_layout_td'-->\r\n";
                            sbTable.Append(tdHeader + tline.TdContent + tdTail);
                        }
                        sbTable.Append("<!--/tr class='line_layout_tr'></table class='line_layout_table'>-->\r\n");
                        sb.Append(sbTable);
                        layoutTableList.Clear();
                    }

                    sb.Append(line);
                    sb.Append("\r\n");
                }
            }

            // 如果最后一行是布局行，此时 list 中还有剩余的没处理。
            if (layoutTableList.Count > 0)
            {
                var sbTable = new StringBuilder();
                // 转换为table，为防止其中的图像链接等不起作用，还是先用注释的方式，然后在 AppendCompile 中继续处理。
                sbTable.Append("<!--table class='line_layout_table'><tr class='line_layout_tr'>-->\r\n");
                foreach (var tline in layoutTableList)
                {
                    var tdHeader = "<!--td class='line_layout_td' style='width:" + tline.WidthText + ";' id='" + tline.TdId + "'==tdId-->\r\n";
                    var tdTail = "\r\n<!--/td class='line_layout_td'-->\r\n";
                    sbTable.Append(tdHeader + tline.TdContent + tdTail);
                }
                sbTable.Append("<!--/tr class='line_layout_tr'></table class='line_layout_table'>-->\r\n");
                sb.Append(sbTable);
                layoutTableList.Clear();
            }

            return sb.ToString();
        }

        public class LayoutTableLine
        {
            public string WidthText { get; set; } = "";
            private string tdContent = "";
            public string TdContent
            {
                get { return this.tdContent; }
                set
                {
                    var text = value.Trim();
                    if (IsImageLinkLine(text))
                    {
                        tdContent = "\r\n" + text + "\r\n";
                    }
                    else tdContent = text;
                }
            }
            public string TdId { get; set; } = "";
        }

        /// <summary>
        /// 判断当前文本行是否用于转换为指定宽度的 td 元素的布局行。形似如下：
        /// 32% xxx
        /// 240px xxx
        /// </summary>
        /// <param name="lineText">文本行。</param>
        /// <param name="tdWidth">用于返回 td 的宽度。</param>
        /// <param name="tdContent">用于返回 td 的内容。</param>
        /// <returns></returns>
        public static bool IsLayoutLine(string lineText, out string tdWidth, out string tdContent, out string tdId)
        {
            Regex regexLayoutLineText = new Regex(@"^[ 　]{0,3}\d{1,}(%|px)[;；]?[  \t]{1,}", RegexOptions.IgnoreCase);
            var matchLayoutLineText = regexLayoutLineText.Match(lineText);
            if (matchLayoutLineText.Success)
            {
                tdWidth = matchLayoutLineText.Value.Trim(new char[] { ';', '；', ' ', '　', '\t', });
                tdContent = lineText.Substring(matchLayoutLineText.Length);

                Regex regexId = new Regex(@"[ 　\t]*[\(（]@[ 　\t]*[a-z][a-z0-9]*[ 　\t]*[\)）][ 　\t]*");
                var matchId = regexId.Match(tdContent);
                if (matchId.Success)
                {
                    tdId = matchId.Value.Trim(new char[] { ' ', '　', '\t', '(', ')', '（', '）', '@', });
                    tdContent = tdContent.Substring(matchId.Length);
                }
                else
                {
                    tdId = "";
                }
                return true;
            }
            else
            {
                tdWidth = "";
                tdContent = "";
                tdId = "";
                return false;
            }
        }

        /// <summary>
        /// 将原型选择题处理为 LME 编译器可以识别的选择题样式。注意：判断题是有两个选项的选择题。
        /// 原型选择题写法：
        /// ``` 选择题
        /// 1. 这是个选择题
        /// A. 选择项A
        /// B. 选择项B
        /// C. 选择项C
        /// D. 选择项D
        /// 【答案】C
        /// 【解析】解析文本。
        /// ……
        /// ```
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        public static string CompilePrototypeChoiceExams(string htmlBody)
        {
            if (string.IsNullOrEmpty(htmlBody)) return "";

            var lines = htmlBody.Split(new string[] { "\r\n", }, StringSplitOptions.None);

            var isOpen = false;
            var result = new StringBuilder();

            var examRegion = new StringBuilder();
            var regChoiceQuestion = new Regex(@"[\(（【][\.、．·…]+[\)）】]");

            foreach (var line in lines)
            {
                if (line.StartsWith("```"))
                {
                    if (isOpen)
                    {
                        // 原型选择题开头必须明确写成： ``` (...) 这样
                        // 但结尾只需要 ``` 即可——禁止在原型选择题书写区域再写这样的源码块！！！
                        // 转换，添加
                        var exampiece = OptionQuestionProcesser.ConvertToExamText(examRegion.ToString());
                        result.Append("\r\n" + exampiece + "\r\n");
                        examRegion = new StringBuilder();
                        isOpen = false;
                        continue;
                    }
                    else
                    {
                        var mark = line.Substring(3).Trim(new char[] { ' ', '　', '\t', '`', }).ToLower();
                        var matchMark = regChoiceQuestion.Match(mark);

                        if (matchMark.Success)
                        {
                            isOpen = true;
                        }
                        else
                        {
                            // 原样
                            result.Append(line + "\r\n");
                        }
                    }
                }
                else
                {
                    if (isOpen)
                    {
                        examRegion.Append(line + "\r\n");
                    }
                    else
                    {
                        result.Append(line + "\r\n");
                    }
                }
            }
            return result.ToString();
        }

        /// <summary>
        /// 编译冒号开头的注释文本行。
        /// </summary>
        /// <param name="lineStrings"></param>
        /// <param name="md"></param>
        /// <param name="sBuilder"></param>
        /// <param name="spanNumber"></param>
        /// <param name="index"></param>
        /// <param name="trimChars"></param>
        private static void CompileVisualComment(string[] lineStrings, ref Markdown md, StringBuilder sBuilder, ref int spanNumber, int index, char[] trimChars)
        {
            // 连续两个 ： 开头的，视为“:[]”。
            var regComment1Dbl = new Regex(@"^[ 　]{0,3}?[:：]{2}");
            var matchComment1Dbl = regComment1Dbl.Match(lineStrings[index]);
            if (matchComment1Dbl.Success)
            {
                lineStrings[index] = "：[]" + lineStrings[index].Substring(matchComment1Dbl.Length);
            }

            string commentText = lineStrings[index].Substring(1);

            // ：[::代码 1]
            // 类似这样的直接转换，变成  ：[dm][dm:代码1]

            Regex regSimpleMark = new Regex(@"[ 　\t]{0,}?[\[【][:：]{2}.{1,}?[\]】]");
            var matchSimpleMark = regSimpleMark.Match(commentText);
            if (matchSimpleMark.Success)
            {
                var mark = matchSimpleMark.Value.TrimStart(new char[] { ':', '：', '[', ']', '【', '】', ' ', '　', '\t', }).TrimEnd(new char[] { ']', '】', });
                var fstMark = mark.Split(new char[] { ' ', '　', '\t', }, StringSplitOptions.RemoveEmptyEntries);
                if (fstMark.Length >= 1)
                {
                    var pinyinMark = ChinesePinYin.ToChinesePinYinText(fstMark[0]).ToLower();
                    if (string.IsNullOrWhiteSpace(pinyinMark) == false)
                    {
                        commentText = "[" + pinyinMark + "][" + pinyinMark + ":" + mark + "]" + commentText.Substring(matchSimpleMark.Length);
                    }
                }
            }

            // 使冒号开头的注释支持自动编号。自动编号的格式为： {1} 或 {^}
            Regex regAutoNumberSpan = new Regex(@"\{[0-9０-９\^]\}");
            var autoNumberSpanMatches = regAutoNumberSpan.Matches(commentText);
            if (autoNumberSpanMatches.Count > 0)
            {
                var textPieces = Utils.Text.SplitToPiecesByRegex(@"\{(\^|([0-9０-９]{1,}?))\}", commentText);
                var trimNumChars = new char[] { ' ', '　', '\t', '{', '}', };
                var sbNum = new StringBuilder();
                foreach (var piece in textPieces)
                {
                    if (piece.IsMartchText == false)
                    {
                        sbNum.Append(piece.SourceText);
                        continue;
                    }

                    if (int.TryParse(piece.SourceText.Trim(trimNumChars), out int num))
                    {
                        spanNumber = num;
                    }
                    else
                    {
                        spanNumber++;
                    }

                    piece.ReplacedText = spanNumber.ToString();
                    sbNum.Append(piece.ReplacedText);
                }

                commentText = sbNum.ToString();
            }

            var webAndWingdingsClassList = new List<string>() { "w0", "w1", "w2", "w3" };
            Regex regCommentSpan = new Regex(@"[【\[]([ 　\t]*[a-zA-Z][a-zA-Z0-9\-_]{0,}[ 　\t]*){0,1}[:：].*?[】\]]");
            var spanMatchs = regCommentSpan.Matches(commentText);
            if (spanMatchs.Count > 0)
            {
                for (int i = spanMatchs.Count - 1; i >= 0; i--)
                {
                    var matchSpan = spanMatchs[i];
                    var spanSrc = matchSpan.Value.Substring(1, matchSpan.Length - 2);
                    var classSplitter = spanSrc.IndexOf(':');
                    if (classSplitter < 0) classSplitter = spanSrc.IndexOf('：');
                    if (classSplitter < 0) continue;
                    var spanClass = spanSrc.Substring(0, classSplitter).Trim(trimChars);
                    var spanContent = spanSrc.Substring(classSplitter + 1).Trim(trimChars);
                    if (string.IsNullOrWhiteSpace(spanClass))
                    {
                        if (string.IsNullOrWhiteSpace(spanContent)) continue;
                        var array = spanContent.Split(trimChars, StringSplitOptions.RemoveEmptyEntries);
                        if (array.Length <= 0) continue;
                        if (string.IsNullOrWhiteSpace(array[0])) continue;
                        string pinYinCssClassId;

                        var cmark = array[0].ToLower();
                        if (array.Length >= 2 && webAndWingdingsClassList.Contains(cmark) && IsDingText(array[1]))
                        {
                            switch (cmark)
                            {
                                case "w0":
                                    {
                                        pinYinCssClassId = "_webdings";
                                        array[0] = "";
                                        spanContent = string.Join(" ", array).Trim();
                                        break;
                                    }
                                case "w1":
                                    {
                                        pinYinCssClassId = "_wingdings";
                                        array[0] = "";
                                        spanContent = string.Join(" ", array).Trim();
                                        break;
                                    }
                                case "w2":
                                    {
                                        pinYinCssClassId = "_wingdings2";
                                        array[0] = "";
                                        spanContent = string.Join(" ", array).Trim();
                                        break;
                                    }
                                case "w3":
                                    {
                                        pinYinCssClassId = "_wingdings3";
                                        array[0] = "";
                                        spanContent = string.Join(" ", array).Trim();
                                        break;
                                    }
                                default:
                                    {
                                        pinYinCssClassId = cmark;
                                        break;
                                    }
                            }
                        }
                        else
                        {
                            pinYinCssClassId = ChinesePinYin.ToChinesePinYinText(array[0]).ToLower();
                        }

                        if (IsCssId(pinYinCssClassId) == false) continue;

                        spanClass = pinYinCssClassId;
                    }
                    else if (IsCssId(spanClass) == false) continue;  // 不符合 CSS 标识符命名规则。

                    commentText = commentText.Substring(0, matchSpan.Index) +
                        "<span " + (spanClass.Length <= 0 ? "" : ("class=\"" + spanClass + "\"")) + ">" + spanContent + "</span>" +
                        commentText.Substring(matchSpan.Index + matchSpan.Length);

                }
            }

            var styleText = "";
            var regStyle = new Regex(@"^[ 　\t]{0,}[\[【][ 　\t]{0,}?[\$￥].*?[\]】]");
            var matchStyle = regStyle.Match(commentText);
            if (matchStyle.Success)
            {
                styleText = ConvertToCssStyle(matchStyle, false, out string mark, true);

                if (string.IsNullOrWhiteSpace(styleText))
                {
                    sBuilder.Append($"<p>{FormatLineContent(ref md, commentText)}</p>\r\n");
                }
                else
                {
                    sBuilder.Append($"<p{styleText}\">{FormatLineContent(ref md, commentText.Substring(matchStyle.Length))}</p>\r\n");
                }
            }
            else
            {
                string lineCssClassText = "comment";

                Regex regPragraphCssClass = new Regex(@"(?<=(^[ 　\t]{0,}))[【\[]([a-zA-Z][a-zA-Z0-9\-_]*)?[\]】]");
                Match match = regPragraphCssClass.Match(commentText);
                if (match.Success)
                {
                    var innerText = match.Value.Substring(1, match.Value.Length - 2).Trim(new char[] { ' ', '　', '\t', });
                    commentText = commentText.Substring(match.Index + match.Length);
                    lineCssClassText = innerText.Trim(trimChars);
                }

                if (string.IsNullOrWhiteSpace(lineCssClassText))
                {
                    sBuilder.Append($"<p>{FormatLineContent(ref md, commentText)}</p>\r\n");
                }
                else
                {
                    sBuilder.Append($"<p class=\"{lineCssClassText}\">{FormatLineContent(ref md, commentText)}</p>\r\n");
                }
            }

            sBuilder.Append("\r\n");
        }

        /// <summary>
        /// 是不是 webdings wingdings wingdings2 wingdings3 这几种字体支持的字符：
        /// 【0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,./'[]\\=-<>?:\"{}|+_)(*&^%$#@!`~ 】
        /// 注意：均为半角，且包含空格字符。
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private static bool IsDingText(string v)
        {
            if (string.IsNullOrWhiteSpace(v)) return false;
            return "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,./'[]\\=-<>?:\"{}|+_)(*&^%$#@!`~ ".IndexOf(v) >= 0;
        }

        /// <summary>
        /// 编译冒号开头的注释行。
        /// 这个方法用于编译 reveal.js 使用的 Html 片段。
        /// </summary>
        /// <returns></returns>
        public static string RevealJS_CompileVisualCommentLine(string lineText, ref Markdown md)
        {
            var sBuilder = new StringBuilder();
            int spanNumber = 1;
            CompileVisualComment(new string[] { lineText, }, ref md, sBuilder, ref spanNumber, 0, new char[] { ' ', '　', '\t', });

            return sBuilder.ToString();
        }

        /// <summary>
        /// 处理 Setext 形式的标题。
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        private static string CompileSetextHeaders(string src)
        {
            if (string.IsNullOrWhiteSpace(src)) return string.Empty;

            var lines = src.Split(new string[] { "\r\n", }, StringSplitOptions.None);
            if (lines.Length <= 1) return src;

            StringBuilder sBuilder = new StringBuilder();
            for (int i = lines.Length - 1; i >= 1; i--)  // Setext 标记必定不会从第0行开始。
            {
                var line = lines[i];
                if (IsHeaderMarkLine(line))     // 三个以上等号组成的标题标记或水平线
                {
                    var preLine = lines[i - 1];
                    if (string.IsNullOrWhiteSpace(preLine))
                    {
                        sBuilder.Insert(0, "<hr class=\"heavy\"/>\r\n");
                        continue;  // 当成水平线
                    }
                    else if (IsHeaderMarkLine(preLine) || IsHorizontalLineText(preLine))
                    {
                        sBuilder.Insert(0, "\r\n");
                        //sBuilder.Insert(0, line);
                        continue;
                    }
                    else
                    {
                        --i;  // 直接跳过当前行的前一行
                        sBuilder.Insert(0, "\r\n");
                        sBuilder.Insert(0, $"<p class='-lme-title-3'>{preLine}</p>");
                        continue;
                    }
                }
                else if (IsHorizontalLineText(line))
                {
                    var preLine = lines[i - 1];
                    if (string.IsNullOrWhiteSpace(preLine))
                    {
                        sBuilder.Insert(0, "<hr/>\r\n");
                        continue;  // 当成水平线
                    }
                    else if (IsHeaderMarkLine(preLine) || IsHorizontalLineText(preLine))
                    {
                        sBuilder.Insert(0, "\r\n");
                        //sBuilder.Insert(0, line);
                        continue;
                    }
                    else
                    {
                        --i;  // 直接跳过当前行的前一行
                        sBuilder.Insert(0, "\r\n");
                        sBuilder.Insert(0, $"<p class='-lme-title-5'>{preLine}</p>");
                        continue;
                    }
                }
                else
                {
                    sBuilder.Insert(0, "\r\n");
                    sBuilder.Insert(0, line);
                }
            }
            return sBuilder.ToString();
        }

        /// <summary>
        /// 编译三个以上等号写的水平线。
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        private static string CompileHeavyHorizontalLines(string src)
        {
            if (string.IsNullOrWhiteSpace(src)) return string.Empty;

            var lines = src.Split(new string[] { "\r\n", }, StringSplitOptions.None);
            if (lines.Length <= 0) return src;

            StringBuilder sBuilder = new StringBuilder();
            foreach (var line in lines)
            {
                if (IsHeaderMarkLine(line))     // 三个以上等号组成的标题标记或水平线
                {
                    sBuilder.Append("<hr class='heavy'/>");
                    sBuilder.Append("\r\n");
                    continue;
                }
                else
                {
                    sBuilder.Append(line);
                    sBuilder.Append("\r\n");
                    continue;
                }
            }
            return sBuilder.ToString();
        }

        public static List<TableLinesInfo> CompileTextTable(string[] lineStrings, ref Markdown md)
        {
            List<TableLinesInfo> tableSourcesList = new List<TableLinesInfo>();
            TableLinesInfo tlInfo = null;

            for (int i = 0; i < lineStrings.Length; i++)
            {
                String lineString = lineStrings[i];

                //表格行第一字符可以不是｜或|，但必须是非空字符。
                if (IsTableRow(lineString))
                {
                    if (lineString.StartsWith("|") == false && lineString.StartsWith("｜") == false)
                    {
                        lineString = "|" + lineString;
                    }

                    //这个移到TableLinesInfo.PreviewFormatTable()方法中去了。
                    //if (lineString.EndsWith("|") == false && lineString.EndsWith("｜") == false)
                    //{
                    //    lineString = lineString + "|";
                    //}
                }

                if (lineString.StartsWith("|") || lineString.StartsWith("｜"))
                // && (lineString.EndsWith("|") || lineString.EndsWith("｜")))
                {
                    if (tlInfo == null)
                    {
                        tlInfo = new TableLinesInfo();
                        tlInfo.StartLineIndex = i;
                    }

                    if (lineString.EndsWith("|^") || lineString.EndsWith("｜^"))
                    {
                        tlInfo.TableLines.Add(new TableLine() { LineText = lineString, Type = TableLineType.MergeLine, });
                    }
                    else if (IsColumnAlignmentDefinitionLine(lineString))
                    {
                        tlInfo.TableLines.Add(new TableLine() { LineText = lineString, Type = TableLineType.ColumnDefinitionLine, });
                    }
                    else if (IsTableCaptionSplittor(lineString))
                    {
                        tlInfo.TableLines.Add(new TableLine() { LineText = lineString, Type = TableLineType.CaptionSplittorLine, });
                    }
                    else { tlInfo.TableLines.Add(new TableLine() { LineText = lineString, Type = TableLineType.Normal, }); }
                }
                else
                {
                    if (tlInfo != null)
                    {
                        tlInfo.EndLineIndex = (i - 1);
                        tableSourcesList.Add(tlInfo);
                        tlInfo = null;
                    }
                }
            }

            // 如果最后一行就是表格最后一行
            if (tlInfo != null)
            {
                tlInfo.EndLineIndex = (lineStrings.Length - 1);
                tableSourcesList.Add(tlInfo);
                tlInfo = null;
            }

            foreach (var info in tableSourcesList)
            {
                info.PreviewFormatTableLines();
            }

            for (int tableIndex = 0; tableIndex < tableSourcesList.Count; tableIndex++)
            {
                TableLinesInfo tableLinesInfo = tableSourcesList[tableIndex];

                int maxColumnsCount = 0;
                String definition = "";
                int definitionLineIndex = -1;

                bool hasColumnDefinitionLine = false;

                for (int j = 0; j < tableLinesInfo.TableLines.Count; j++)
                {
                    var tableLine = tableLinesInfo.TableLines[j];
                    String lineString = tableLine.LineText;

                    if (tableLine.Type == TableLineType.ColumnDefinitionLine)
                    {
                        definition = lineString;
                        definitionLineIndex = j;

                        definition = definition.Replace("｜", "\r\n")
                                .Replace("|", "\r\n").Replace("：", ":")
                                .Replace("－", "-").Replace(" ", "")
                                .Replace("　", "").Replace("\t", "");


                        String[] definitionTexts = definition.Split(new char[2] { '\r', '\n' },
                            StringSplitOptions.RemoveEmptyEntries);

                        foreach (String definitionText in definitionTexts)
                        {
                            if (definitionText.Length == 0)
                                continue;

                            bool left = false;
                            bool right = false;
                            if (definitionText.StartsWith(":") || definitionText.StartsWith("^:"))
                                left = true;

                            if (definitionText.EndsWith(":") || definitionText.EndsWith(":^"))
                                right = true;

                            ColumnAlignment cDefinition;
                            if (left && right)
                            {
                                left = right = false;
                                cDefinition = ColumnAlignment.CENTER;
                            }
                            else if (right)
                            {
                                cDefinition = ColumnAlignment.RIGHT;
                            }
                            else
                            {
                                cDefinition = ColumnAlignment.LEFT;
                            }

                            tableLinesInfo.ColumnAlignments.Add(cDefinition);
                        }

                        hasColumnDefinitionLine = true;
                    }
                    else
                    {
                        var commonLine = lineString.Replace("|", "\t").Replace("｜", "\t");
                        if (commonLine.EndsWith("\t"))
                        {
                            commonLine = commonLine.Substring(0, commonLine.Length - 1);
                        }

                        if (commonLine.StartsWith("\t"))
                        {
                            commonLine = commonLine.Substring(1);
                        }

                        String[] spanStrings = commonLine.Split('\t');
                        maxColumnsCount = Math.Max(maxColumnsCount, spanStrings.Length);
                    }

                }// 取出表的各列定义（主要是对齐）

                // 如果没有定义，则自动定义列（取最多的一行定义）。
                if (false == hasColumnDefinitionLine)
                {
                    for (int j = 0; j < tableLinesInfo.TableLines.Count; j++)
                    {
                        String lineString = tableLinesInfo.TableLines[j].LineText
                                .Replace("|", "\t").Replace("｜", "\t");
                        if (lineString.EndsWith("\t"))
                        {
                            lineString = lineString.Substring(0, lineString.Length - 1);
                        }

                        if (lineString.StartsWith("\t"))
                        {
                            lineString = lineString.Substring(1);
                        }

                        String[] spanStrings = lineString.Split('\t');

                        maxColumnsCount = Math.Max(maxColumnsCount, spanStrings.Length);
                    }

                    for (int i = 1; i <= maxColumnsCount; i++)
                    {
                        tableLinesInfo.ColumnAlignments.Add(ColumnAlignment.LEFT);// 默认全左齐。
                    }
                }

                StringBuilder tableBuilder = new StringBuilder();
                if (Globals.MainWindow.TableCaptionAtBottom)
                {
                    tableBuilder.Append("<table style=\"margin-bottom:0.2em;\">");
                }
                else tableBuilder.Append("<table>");

                var tableCaptionHtml = "";

                for (int tableLineIndex = 0; (tableLineIndex < definitionLineIndex && tableLineIndex < tableLinesInfo
                        .TableLines.Count); tableLineIndex++)
                {
                    // 表头部分
                    String lineString = tableLinesInfo.TableLines[tableLineIndex].LineText;

                    Regex regex = new Regex(@"[ 　\t]{1,}");
                    lineString = regex.Replace(lineString, " ");
                    Regex regex2 = new Regex(@"[ 　\t]{0,}[|｜][ 　\t]{0,}");
                    lineString = regex2.Replace(lineString, "|");

                    if (lineString.StartsWith("|") || lineString.StartsWith("｜"))
                        lineString = lineString.Substring(1);

                    if (lineString.EndsWith("|") || lineString.EndsWith("｜"))
                        lineString.Substring(0, lineString.Length - 2);

                    lineString = lineString.Replace("｜", "\t").Replace("|", "\t");

                    // 去除头尾。
                    if (lineString.EndsWith("\t"))
                    {
                        lineString = lineString.Substring(0, lineString.Length - 1);
                    }

                    String[] spanStrings = lineString.Split('\t');

                    if (spanStrings.Length > 0)
                    {
                        if (tableLineIndex == 0 && spanStrings.Length == 1)
                        {
                            if (Globals.MainWindow.TableCaptionAtBottom == false)
                            {
                                tableCaptionHtml = $"<caption>{spanStrings[0]}</caption>\n";
                                tableBuilder.Append(tableCaptionHtml);
                            }
                            else
                            {
                                tableCaptionHtml = $"<p style=\"margin-bottom:1em;margin-top:0;text-indent:0;text-align:center;font-size:1em;font-weight:bold;\">表{tableIndex + 1} {spanStrings[0]}</p>\n";
                                //加到末尾。
                            }
                            continue;
                        }

                        tableBuilder.Append("<tr>");

                        var colSpanCount = 1;
                        var trimChars = new char[] { ' ', '　', '\t', };

                        for (int spanIndex = 0;
                            spanIndex < Math.Max(Math.Max(spanStrings.Length, tableLinesInfo.ColumnAlignments.Count), maxColumnsCount);
                            spanIndex++)
                        {
                            if (colSpanCount > 1)
                            {
                                --colSpanCount;
                                continue;
                            }

                            if (spanIndex >= spanStrings.Length)
                            {
                                //感觉表头还是全部居中比较好看。
                                tableBuilder.Append("<th style=\"TEXT-ALIGN: center;\">");
                                tableBuilder.Append("</th>");
                                // 添加空单元格。

                                continue;// 防止用户一行末尾未填空内容。
                            }

                            string s = ConvertChar(spanStrings[spanIndex]);

                            Regex regColSpan = new Regex(@"(?<=([ 　]{1,}))[\-－][\>》〉]{1,}?$");
                            var marColSpan = regColSpan.Match(s);
                            colSpanCount = marColSpan.Length;
                            var colSpanCountTxt = "";
                            if (marColSpan.Success)
                            {
                                colSpanCountTxt = " colspan=\"" + colSpanCount.ToString() + "\"";
                                s = s.Substring(0, s.Length - marColSpan.Length).Trim(trimChars);
                            }

                            //感觉表头还是全部居中对齐比较好看。
                            tableBuilder.Append($"<th style=\"TEXT-ALIGN: center;\"{colSpanCountTxt}>");
                            tableBuilder.Append(s == "" ? "　" : FormatLineContent(ref md, s));
                            tableBuilder.Append("</th>");
                        }
                        tableBuilder.Append("</tr>");
                    }
                }

                for (int index = definitionLineIndex + 1; index < tableLinesInfo
                        .TableLines.Count; index++)
                {
                    // 表体部分
                    String lineString = tableLinesInfo.TableLines[index].LineText;

                    Regex regex1 = new Regex(@"[ 　\t]{1,}");
                    lineString = regex1.Replace(lineString, " ");
                    Regex regex2 = new Regex(@"[ 　\t]{0,}[|｜][ 　\t]{0,}");
                    lineString = regex2.Replace(lineString, "|");

                    if (lineString.StartsWith("|") || lineString.StartsWith("｜"))
                        lineString = lineString.Substring(1);

                    if (lineString.EndsWith("|") || lineString.EndsWith("｜"))
                        lineString = lineString.Substring(0, lineString.Length - 1);

                    lineString = lineString.Replace("｜", "\t").Replace("|", "\t");

                    // 去除头尾。
                    if (lineString.EndsWith("\t"))
                    {
                        lineString = lineString.Substring(0, lineString.Length - 1);
                    }

                    String[] spanStrings = lineString.Split('\t');

                    if (spanStrings.Length > 0)
                    {
                        tableBuilder.Append("<tr>");

                        var colSpanCount = 1;
                        var trimChars = new char[] { ' ', '　', '\t', };
                        for (int spanIndex = 0;
                            spanIndex < Math.Max(Math.Max(spanStrings.Length, tableLinesInfo.ColumnAlignments.Count), maxColumnsCount);
                            spanIndex++)
                        {
                            if (colSpanCount > 1)
                            {
                                --colSpanCount;
                                continue;
                            }

                            //这行补齐会造成错误
                            //if (spanIndex >= tableLinesInfo.ColumnAlignments.Count)
                            //{
                            //    tableBuilder.Append("<td>");
                            //}

                            if (spanIndex >= spanStrings.Length)
                            {
                                // 要取出对齐
                                ColumnAlignment align = GetColumnAlign(tableLinesInfo.ColumnAlignments, spanIndex);

                                switch (align)
                                {
                                    case ColumnAlignment.CENTER:
                                        {
                                            tableBuilder
                                                    .Append("<td style=\"TEXT-ALIGN: center;\">");
                                            break;
                                        }
                                    case ColumnAlignment.RIGHT:
                                        {
                                            tableBuilder
                                                    .Append("<td style=\"TEXT-ALIGN: right;\">");
                                            break;
                                        }
                                    default:
                                        tableBuilder
                                                .Append("<td style=\"TEXT-ALIGN: left;\">");
                                        break;
                                }
                                tableBuilder.Append("　</td>\n");
                                // 添加内容为一个全角空格的单元格。

                                continue;// 防止用户一行末尾未填空内容。
                            }

                            string s = ConvertChar(spanStrings[spanIndex]).Trim(trimChars);

                            Regex regColSpan = new Regex(@"(?<=([ 　]{1,}))[\-－][\>》〉]{1,}?$");
                            var marColSpan = regColSpan.Match(s);
                            colSpanCount = marColSpan.Length;
                            var colSpanCountTxt = "";
                            if (marColSpan.Success)
                            {
                                colSpanCountTxt = " colspan=\"" + colSpanCount.ToString() + "\"";
                                s = s.Substring(0, s.Length - marColSpan.Length).Trim(trimChars);
                            }

                            // 要取出对齐
                            ColumnAlignment align2 = GetColumnAlign(tableLinesInfo.ColumnAlignments, spanIndex);

                            switch (align2)
                            {
                                case ColumnAlignment.CENTER:
                                    {
                                        tableBuilder
                                                .Append($"<td style=\"TEXT-ALIGN: center;\"{colSpanCountTxt}>");
                                        break;
                                    }
                                case ColumnAlignment.RIGHT:
                                    {
                                        tableBuilder
                                                .Append($"<td style=\"TEXT-ALIGN: right;\"{colSpanCountTxt}>");
                                        break;
                                    }
                                default:
                                    tableBuilder
                                            .Append($"<td style=\"TEXT-ALIGN: left;\"{colSpanCountTxt}>");
                                    break;
                            }
                            tableBuilder.Append(s == "" ? "　" : FormatLineContent(ref md, s));     //单元格的内容
                            tableBuilder.Append("</td>\n");
                        }
                        tableBuilder.Append("</tr>\n");
                    }
                }

                tableBuilder.Append("</table>\n");

                if (Globals.MainWindow.TableCaptionAtBottom)
                {
                    tableBuilder.Append(tableCaptionHtml);
                }

                tableLinesInfo.TableHtmlText = tableBuilder.ToString();
            }

            return tableSourcesList;
        }

        public static bool IsRegionStart(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex regexStart = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,}?)?([！？!?ＩｉＷｗＥｅＱｑIiWwEeQq][ 　\t]*)?\{[\+\-＋－]{0,1}");
            return regexStart.Match(lineText).Success;
        }

        public static bool IsRegionEnd(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            Regex regexEnd = new Regex(@"^[ 　]{0,3}\}[ 　\t]*([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?");
            return regexEnd.Match(lineText).Success;
        }

        /// <summary>
        /// LME 在这里很严格，只支持以字母开头，包括字母、数字和下划线、连字符的情况。
        /// </summary>
        /// <param name="txt"></param>
        /// <returns></returns>
        private static bool IsCssId(string txt)
        {
            if (string.IsNullOrWhiteSpace(txt)) return false;
            foreach (char c in txt)
            {
                if ("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-".Contains(c) == false) return false;
            }

            if (txt.Length <= 0) return false;
            if ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".Contains(txt[0]) == false) return false;

            return true;
        }

        /// <summary>
        /// 取 Header 标题的层级索引值。
        /// </summary>
        /// <param name="lI1">一级标题会使前面所有高于一级的标题索引重置，并使一级标题的索引值加1。</param>
        /// <param name="lI2">二级标题会使前面所有高于二级的标题索引重置，并使二级标题的索引值加1。</param>
        /// <param name="lI3">三级标题会使前面所有高于三级的标题索引重置，并使三级标题的索引值加1。</param>
        /// <param name="lI4">四级标题会使前面所有高于四级的标题索引重置，并使四级标题的索引值加1。</param>
        /// <param name="lI5">五级标题会使前面所有六级标题索引重置，并使五级标题的索引值加1。</param>
        /// <param name="lI6">六级标题只会使六级标题索引加1。</param>
        /// <returns></returns>
        public static string BuildHeaderIndexText(int lI1, int lI2, int lI3, int lI4, int lI5, int lI6, string autoNumberStyle)
        {
            switch (autoNumberStyle)
            {
                case "zh_cn":
                    {
                        if (lI6 > 0) return $"「{lI6}」";
                        if (lI5 > 0) return $"『{lI5}』";
                        if (lI4 > 0) return $"（{lI4}）";
                        if (lI3 > 0) return $"{lI3}．";
                        if (lI2 > 0) return $"（{DateTimeConverter.YearToCn(lI2)}）";
                        if (lI1 > 0) return $"{DateTimeConverter.YearToCn(lI1)}、";
                        return "";
                    }
                default:
                    {
                        if (lI1 <= 0) return "";

                        StringBuilder sb = new StringBuilder();
                        sb.Append(lI1);

                        if (lI2 <= 0) return sb.ToString() + ".";
                        sb.Append("." + lI2);
                        if (lI3 <= 0) return sb.ToString() + ".";
                        sb.Append("." + lI3);
                        if (lI4 <= 0) return sb.ToString() + ".";
                        sb.Append("." + lI4);
                        if (lI5 <= 0) return sb.ToString() + ".";
                        sb.Append("." + lI5);
                        if (lI6 <= 0) return sb.ToString() + ".";
                        sb.Append("." + lI6);

                        return sb.ToString() + ".";
                    }
            }
        }

        /// <summary>
        /// 取 Header 标题的层级。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <param name="headerMark">传出 Header 标题的标志文本。</param>
        /// <param name="headerContent">传出 Header 标题的内容文本。</param>
        public static int HeaderLevel(string lineText, out string headerMark, out string headerContent)
        {
            if (string.IsNullOrWhiteSpace(lineText))
            {
                headerMark = "";
                headerContent = lineText;
                return 0;
            }

            Regex regex = new Regex("^ {0,3}[＃#]{1,6}");
            var match = regex.Match(lineText);
            if (match != null && match.Success)
            {
                var count = 0;
                foreach (char c in match.Value)
                {
                    if (c == '#' || c == '＃') count++;
                }
                headerMark = match.Value;
                headerContent = lineText.Substring(match.Length);
                return count;
            }

            headerMark = "";
            headerContent = lineText;
            return 0;
        }

        /// <summary>
        /// 是否二维文字表行。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsTableRow(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return false;

            if (lineText.Contains("|") == false && lineText.Contains("｜") == false) return false;

            if (lineText.StartsWith("    ") || lineText.StartsWith("　　　　") || lineText.StartsWith("\t")) return false;

            var trim = lineText.Trim();
            if (trim.StartsWith("+ ") || trim.StartsWith("* ") || trim.StartsWith("- ")) return false;

            if (lineText.StartsWith(">") || lineText.StartsWith("》")) return false;

            return true;
        }

        /// <summary>
        /// 处理转义字符：
        /// \*
        /// \\
        /// \`
        /// \_
        /// 看起来只需要处理这四个转义，再加自定义的删除标记即可。2021年10月13日。其它转义貌似有些过于激进了。
        /// \{
        /// \}
        /// \[
        /// \]
        /// \(
        /// \)
        /// \#
        /// \+
        /// \-
        /// \.
        /// \!
        /// </summary>
        /// <param name="sourceText">源文本。</param>
        public static string ConvertChar(string sourceText)
        {
            sourceText = sourceText.Replace("[=", "<s>").Replace("[＝", "<s>").Replace("【＝", "<s>").Replace("【=", "<s>")
                        .Replace("=]", "</s>").Replace("＝】", "</s>").Replace("=】", "</s>").Replace("＝]", "</s>")
                        .Replace("[\\=", "[=").Replace("[\\＝", "[=").Replace("【\\＝", "[=").Replace("【\\=", "[=")
                        .Replace("=\\]", "=]").Replace("＝\\】", "=]").Replace("=\\】", "=]").Replace("＝\\]", "=]");

            sourceText = sourceText.Replace("\\*", "&#42;").Replace("\\\\", "&#92;").Replace("\\`", "&#96;").Replace("\\_", "&#95;").Replace("\\^", "&#94;")
                .Replace("[\\=", "&#91;=").Replace("=\\]", "=&#93;");

            //.Replace("\\{", "&#123;").Replace("\\}", "&#125;").Replace("\\[", "&#91;")
            //.Replace("\\]", "&#93;").Replace("\\(", "&#40;").Replace("\\)", "&#41;").Replace("\\#", "&#35;")
            //.Replace("\\+", "+").Replace("\\-", "-").Replace("\\.", ".").Replace("\\!", "!");
            return sourceText;
        }

        /// <summary>
        /// 取二维文字表列对齐方式。
        /// </summary>
        /// <param name="columnDefinitions">列定义对象集合。</param>
        /// <param name="index">索引值，指定哪一列。</param>
        private static ColumnAlignment GetColumnAlign(List<ColumnAlignment> columnDefinitions, int index)
        {
            if (index < 0 || index >= columnDefinitions.Count
                    || columnDefinitions.Count == 0)
                return ColumnAlignment.LEFT;

            return columnDefinitions[index];
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="tableLinesInfos"></param>
        /// <returns></returns>
        private static TableLinesInfo InWhitchTable(int index, List<TableLinesInfo> tableLinesInfos)
        {
            foreach (TableLinesInfo t in tableLinesInfos)
            {
                if (index >= t.StartLineIndex && index <= t.EndLineIndex)
                    return t;
            }

            return null;
        }

        public static bool IsColumnAlignmentDefinitionLine(string text)
        {
            if (string.IsNullOrEmpty(text)) return false;
            if (text.Contains("-") == false && text.Contains("－") == false) return false;//防止将空表行当作表的“列对齐定义行”。

            foreach (var c in text)
            {
                if (c == '-' || c == '－' || c == '|' || c == '｜' || c == ' '
                    || c == '　' || c == '\t' || c == ':' || c == '：' || c == '^')
                    continue;//^表示该列应自动顺序编号
                return false;
            }

            return true;
        }

        public static bool IsNumber(char @char)
        {
            if ((@char >= '1' && @char <= '9') || @char == '0') return true;
            if ((@char >= '１' && @char <= '９') || @char == '０') return true;

            return false;
        }

        /// <summary>
        /// 是否由连字符-、－组成的水平线。
        /// </summary>
        public static bool IsHorizontalLineText(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return false;
            if (lineText.StartsWith("\t")) return false;//以tab开头的是代码块<code>

            if (lineText.Replace("　", " ").StartsWith("    ")) return false;  // 代码块

            var countOfMarkChars = 0;
            foreach (char c in lineText)
            {
                if (c == '-' || c == '－')
                    countOfMarkChars++;

                if (c != ' ' && c != '　' && c != '-' && c != '－' && c != '\t')//中间带tab符也算，但不能以tab符开头。
                    return false;
            }

            if (countOfMarkChars < 3) return false;

            return true;
        }

        /// <summary>
        /// 是否由一系列等号组成的水平线。用来标记当前行前面一行非空、非代码块的行为标题。
        /// （默认h3级，实际上只是个加粗段落 css class : p.-lme-title-3）
        /// </summary>
        /// <param name="lineText"></param>
        /// <returns></returns>
        public static bool IsHeaderMarkLine(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return false;
            if (lineText.StartsWith("\t")) return false;//以tab开头的是代码块<code>

            if (lineText.Replace("　", " ").StartsWith("    ")) return false;  // 代码块

            foreach (char c in lineText)
            {
                if (c != ' ' && c != '　' && c != '=' && c != '＝' && c != '\t')//中间带tab符也算，但不能以tab符开头。
                    return false;
            }

            return (lineText.Contains("=") || lineText.Contains("＝"));
        }

        /// <summary>
        /// 判断此行文本是否只包含“｜”、“=”等。这样的行是“表格格式化功能”自动添加的分割表标题与表头行的装饰行。
        /// </summary>
        /// <param name="lineText">行文本。</param>
        public static bool IsTableCaptionSplittor(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return false;

            var hasCellSplitter = false;
            foreach (char c in lineText)
            {
                if (c == '｜' || c == '|')
                {
                    if (hasCellSplitter == false)
                    {
                        hasCellSplitter = true;
                    }
                    continue;
                }

                if (c == '=' || c == '＝') continue;

                return false;
            }
            return hasCellSplitter;
        }

        /// <summary>
        /// 任务列表的状态。
        /// </summary>
        public enum TaskListItemState
        {
            NotTaskListItem, UnStart, Precessing, Finished, Aborted
        }

        public static string FormatMaterials(string srcMarkdownText)
        {
            if (string.IsNullOrWhiteSpace(srcMarkdownText)) return "";

            var pieces = Utils.Text.SplitToPiecesByRegex(@"＜＜材料＞＞[\s\S]*?(?=(＜＜材料＞＞|＜＜出处＞＞|＜＜问题＞＞))", srcMarkdownText);

            var formatCount = 0;
            for (int i = 0; i < pieces.Count; i++)
            {
                var piece = pieces[i];
                if (piece.IsMartchText == false || piece.SourceText.Length < 12) continue;

                var txt = Text.FormatLineSplitter(piece.SourceText.Substring(6)).Trim(new char[] { '\r', '\n' });
                var header = piece.SourceText.Substring(0, 6);
                var lines = txt.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

                var hasImageLinkOrTableLine = false;
                var hasImageLink = false;
                foreach (var line in lines)
                {
                    if (string.IsNullOrWhiteSpace(line)) continue;
                    if (CustomMarkdownSupport.IsImageLinkLine(line))
                    {
                        hasImageLinkOrTableLine = true;
                        hasImageLink = true;
                        break;
                    }

                    if (line.Contains("|") || line.Contains("｜"))
                    {
                        hasImageLinkOrTableLine = true;
                        break;
                    }
                }

                if (hasImageLinkOrTableLine == false)
                {
                    piece.ReplacedText = header + txt.Replace("\r\n", "") + "\r\n";
                    formatCount++;
                }
                else
                {
                    if (hasImageLink)
                    {
                        piece.ReplacedText = piece.SourceText.Replace("![", "\r\n＜＜材料＞＞![");
                        formatCount++;
                    }
                    else
                    {
                        piece.ReplacedText = null;
                    }
                }
            }

            if (formatCount <= 0) return srcMarkdownText;

            var sb = new StringBuilder();
            foreach (var piece in pieces)
            {
                if (piece.IsMartchText == false)
                {
                    sb.Append(piece.SourceText);
                    continue;
                }

                if (piece.ReplacedText != null)
                {
                    sb.Append(piece.ReplacedText);
                }
                else
                {
                    sb.Append(piece.SourceText);
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// 取格式化后的 Markdown 文本。
        /// </summary>
        /// <param name="srcMarkdownText">尚未格式化的 Markdown 文本。</param>
        /// <returns></returns>
        public static string FormatMarkdownText(string srcMarkdownText)
        {
            var trimChars = new char[] { ' ', '　', '\t', };
            string splitter = "\n";  //复制过来的东西可能只有 \n

            var lines = srcMarkdownText.Split(new string[] { splitter }, StringSplitOptions.None);//注意：空行有用，引用和正常文本间的切换需要空行。

            splitter = "\r\n";

            var sb = new StringBuilder();
            var previewHeaderLevel = 0;
            var orderListNumber = 0;
            for (int i = 0; i < lines.Length; i++)
            {
                var text = lines[i].Replace("\r", "");// .Replace("\n", ""); 这个不需要了。

                #region 保留空行
                var trimedText = text.TrimStart(new char[] { ' ', '　', '\t' });

                if (string.IsNullOrEmpty(trimedText))//保留空行。
                {
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 代码块原样输出，非代码块则处理一下 QuickImageLink （快捷图像链接）
                if (text.StartsWith("    ") || text.StartsWith("\t"))
                {
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                else
                {
                    var pieces = Utils.Text.SplitToPiecesByRegex(@"[\(（][:：][a-zA-Z0-9\-_\.\\/]{2,}?[:：][\)）]", text);
                    foreach (var piece in pieces)
                    {
                        if (piece.IsMartchText)
                        {
                            var shortName = piece.SourceText.Substring(2, piece.SourceText.Length - 4).Trim(new char[] { ' ', '　', '\t', });
                            var fi = new FileInfo(Globals.PathofWorkspaceImages + shortName);
                            if (string.IsNullOrEmpty(fi.Extension))
                            {
                                var extension = shortName.EndsWith(".") ? "png" : ".png";
                                if (File.Exists(Globals.PathofWorkspaceImages + shortName + extension))  // 默认支持 png
                                {
                                    shortName += extension;
                                }
                            }

                            piece.ReplacedText = "(:" + shortName + ":)";
                        }
                    }
                    text = Utils.Text.JoinReplacedList(pieces);
                }
                #endregion

                #region 格式化被嵌入到引用块中的标题
                Match match2;
                if (IsHeaderInBlockQuote(text, out match2))
                {
                    if (match2 != null && match2.Success)
                    {
                        StringBuilder sbTitleAfterBlockQuoter = new StringBuilder();
                        foreach (char c in match2.Value)
                        {
                            if (c == '#' || c == '＃')
                            {
                                sbTitleAfterBlockQuoter.Append('#');
                            }
                        }
                        text = sbTitleAfterBlockQuoter.ToString() + text.Substring(match2.Length);
                    }
                }
                #endregion

                #region 格式化标题
                if (IsHeaderLine(text))
                {
                    orderListNumber = 0;

                    Regex reg = new Regex(@"^[ 　]{0,3}[#＃]{1,6}");
                    var match = reg.Match(text);
                    if (match != null && match.Success)
                    {
                        //防止出现后一个标题比前一个层级高2层以上的情况
                        //例如，前一个标题是一级，紧跟着的一个标题却是三级，这不合逻辑。
                        string titleHeader;
                        string titleTail;
                        var titleLevel = HeaderLevel(text, out titleHeader, out titleTail);
                        if (titleLevel > previewHeaderLevel + 1)
                        {
                            titleLevel = previewHeaderLevel + 1;
                            previewHeaderLevel = titleLevel;

                            StringBuilder sbHeader = new StringBuilder();
                            for (int x = 0; x < titleLevel; x++)
                            {
                                sbHeader.Append("#");
                            }
                            titleHeader = sbHeader.ToString();
                            sb.Append(titleHeader);
                            sb.Append(" ");
                            sb.Append(titleTail.TrimStart(trimChars));
                            sb.Append(splitter);
                            continue;
                        }
                        else
                        {
                            previewHeaderLevel = titleLevel;

                            sb.Append(match.Value.Replace("＃", "#"));
                            sb.Append(" ");
                            sb.Append(text.Substring(match.Length).TrimStart(trimChars));
                            sb.Append(splitter);
                            continue;
                        }
                    }
                }
                #endregion

                #region 日期行
                if (IsDateLine(text))
                {
                    sb.Append(FormatDateLine(text));
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region TODO 型特殊注释文本行
                string newTodoCommentMark;
                string newTodoCommentTail;
                if (IsTodoCommentLine(text, out newTodoCommentMark, out newTodoCommentTail, "TODO"))
                {
                    sb.Append("；TODO：" + newTodoCommentTail);
                    sb.Append(splitter);
                    continue;
                }

                if (IsTodoCommentLine(text, out newTodoCommentMark, out newTodoCommentTail, "DONE"))
                {
                    sb.Append("；DONE：" + newTodoCommentTail);
                    sb.Append(splitter);
                    continue;
                }

                if (IsTodoCommentLine(text, out newTodoCommentMark, out newTodoCommentTail, "DOING"))
                {
                    sb.Append("；DOING：" + newTodoCommentTail);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region [Menu] 型特殊注释文本行
                string newMenuMarkTail;
                if (IsMenuMarkLine(text, out newMenuMarkTail))
                {
                    sb.Append("；[Menu]：" + newMenuMarkTail);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 演示模式 型特殊注释文本行

                string newPresentationTypeTail;
                if (IsPresentationTypeLine(text, out newPresentationTypeTail))
                {
                    sb.Append("；PM：" + newPresentationTypeTail);
                    sb.Append(splitter);
                    continue;
                }

                #endregion

                #region 行首问号统一为全角
                // 宋体是默认字体，半角视觉效果很差
                var questionMarks = Regex.Match(text, @"^[\?？]+");
                if (questionMarks.Success)
                {
                    sb.Append(new string('？', questionMarks.Length));
                    sb.Append(text.Substring(questionMarks.Length).Trim(trimChars));
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 格式化双句号行
                if (IsBoldHeaderLine2(text, "。", out string newBoldHeaderLineHeaderByPeriod, out string newBoldHeaderLineTailByPeriod, out string formatedMarkByPeriod))
                {
                    sb.Append(ReplaceMuiltyMarks(newBoldHeaderLineHeaderByPeriod) + "。。" + newBoldHeaderLineTailByPeriod);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 格式化双问号行
                if (IsBoldHeaderLine2(text, "[?？]", out string newBoldHeaderLineHeaderByQuestion, out string newBoldHeaderLineTailByQuestion, out string formatedMarkByQuestion))
                {
                    sb.Append(ReplaceMuiltyMarks(newBoldHeaderLineHeaderByQuestion) + (formatedMarkByQuestion.Length > 4 ? "？？？？" : formatedMarkByQuestion) + newBoldHeaderLineTailByQuestion);    // 支持多个
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 格式化双惊叹号行
                if (IsBoldHeaderLine2(text, "[!！]", out string newBoldHeaderLineHeaderByExclamation, out string newBoldHeaderLineTailByExclamation, out string formatedMarkByExclamation))
                {
                    sb.Append(ReplaceMuiltyMarks(newBoldHeaderLineHeaderByExclamation) + (formatedMarkByExclamation.Length > 4 ? "！！！！" : formatedMarkByExclamation) + newBoldHeaderLineTailByExclamation);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                // 格式化双冒号、双逗号要放到后面来。因为它们不能与句尾多标点标记同时使用。

                #region 格式化双冒号行
                if (IsBoldHeaderLine(text, "[:：]", out string newBoldHeaderLineHeaderByColon, out string newBoldHeaderLineTailByColon, out string formatedMarkByColon))
                {
                    sb.Append(ReplaceMuiltyMarks(newBoldHeaderLineHeaderByColon) + "：：" + newBoldHeaderLineTailByColon);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 格式化双逗号行
                if (IsBoldHeaderLine(text, "[,，]", out string newBoldHeaderLineHeaderByComma, out string newBoldHeaderLineTailByComma, out string formatedMarkByComma))
                {
                    sb.Append(ReplaceMuiltyMarks(newBoldHeaderLineHeaderByComma) + "，，" + newBoldHeaderLineTailByComma);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 与试题相关的行
                if (MarkdownEditorBase.IsStartWithTestPaperKeyWord(text))
                {
                    //text = trimedText;//试题元素前的全角空格需要保留。
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 备注，以分号或冒号开头
                if (IsCommentLine(text))
                {
                    //不再要求顶格，保留空格。全格式化成半角空格。
                    var commentStartIndex = 1;
                    var isCompileComment = false;
                    for (int iComment = 0; iComment < text.Length; iComment++)
                    {
                        var c = text[iComment];
                        if (c == ' ') continue;
                        else if (c == '　') continue;
                        else if (c == '\t') continue;
                        else if (c == '；' || c == ';')
                        {
                            commentStartIndex = iComment + 1;
                            break;
                        }
                        else if (c == '：' || c == ':')
                        {
                            commentStartIndex = iComment + 1;
                            isCompileComment = true;
                            break;
                        }
                    }

                    if (isCompileComment)
                    {
                        var regComment1Dbl = new Regex(@"^[ 　]{0,3}?[:：]{2}");
                        var matchComment1Dbl = regComment1Dbl.Match(text);

                        string commentText;
                        bool isDoubleComment = false;
                        if (matchComment1Dbl.Success)
                        {
                            isDoubleComment = true;
                            commentText = text.Substring(matchComment1Dbl.Length);
                            text = "::" + commentText;
                        }
                        else
                        {
                            commentText = text.Substring(commentStartIndex);
                        }


                        Regex regCssClass = new Regex(@"(?<=(^[ 　\t]{0,}))[【\[].*?[\]】]");
                        Match match = regCssClass.Match(commentText);
                        if (match.Success)
                        {
                            var trimSimpleStyle = new char[] { ' ', '　', '\t', };
                            var innerText = match.Value.Substring(1, match.Value.Length - 2).Trim();
                            if (innerText.StartsWith("$") || innerText.StartsWith("￥"))
                            {
                                innerText = FormatSimpleStyleText(trimSimpleStyle, innerText.Trim(new char[] { ' ', '　', '\t', '$', '￥', }), false);
                            }
                            commentText = commentText.Substring(match.Index + match.Length);
                            if (isDoubleComment)
                            {
                                text = "::[" + innerText + "]" + commentText.Trim(new char[] { ' ', '　', '\t', });
                            }
                            else
                            {
                                text = "：[" + innerText + "]" + commentText.Trim(new char[] { ' ', '　', '\t', });
                            }
                        }
                        else
                        {
                            if (isDoubleComment)
                            {
                                text = "::" + commentText.Trim(new char[] { ' ', '　', '\t', });
                            }
                            else
                            {
                                text = "：" + commentText.Trim(new char[] { ' ', '　', '\t', });
                            }
                        }
                    }
                    else
                    {
                        text = "；" + text.Substring(commentStartIndex);
                    }
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 如果是在树型文字表中的备注行（：开头注释行）

                Regex regCommentInTreeTextTable = new Regex(@"^([!！][　├│└]{0,}){1,}[：:]+");
                var matchCommentInTreeTextTable = regCommentInTreeTextTable.Match(text);
                if (matchCommentInTreeTextTable.Success)
                {
                    var header = matchCommentInTreeTextTable.Value;
                    if (header.EndsWith(":"))
                    {
                        header = header.Substring(0, header.Length - 1) + "：";
                    }

                    text = header + text.Substring(matchCommentInTreeTextTable.Length);
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }

                #endregion

                #region 任务列表，必须顶格，否则易与Code块冲突
                //形式是：[-][%][+][#]，减号表示未开始，%号表示正在进行，加号表示已完成，[#]表示废弃。
                //两个方括弧间可以有空格。
                if (IsTaskLine(text))
                {
                    TaskListItemState state = TaskListItemState.UnStart;
                    int index = text.IndexOf(']');
                    char[] trimArray = new char[] { ' ', '\t', '　' };
                    var t1 = text.Substring(0, index + 1).Trim(trimArray);
                    var t2 = text.Substring(index + 1);//跳过 ]

                    foreach (char c in t1)
                    {
                        if (c == '[' || c == ']' || c == ' ' || c == '\t' || c == '　') continue;//允许空格。
                        if (c == '-' || c == '－')
                        {
                            state = TaskListItemState.UnStart;
                            break;
                        }
                        else if (c == '%')
                        {
                            state = TaskListItemState.Precessing;
                            break;
                        }
                        else if (c == '+')
                        {
                            state = TaskListItemState.Finished;
                            break;
                        }
                        else
                        {
                            state = TaskListItemState.NotTaskListItem;
                            break;
                        }
                    }

                    switch (state)
                    {
                        case TaskListItemState.UnStart:
                            {
                                text = "[-] " + t2.TrimStart(trimArray);
                                break;
                            }
                        case TaskListItemState.Finished:
                            {
                                text = "[+] " + t2.TrimStart(trimArray);
                                break;
                            }
                        case TaskListItemState.Precessing:
                            {
                                text = "[%] " + t2.TrimStart(trimArray);
                                break;
                            }
                        case TaskListItemState.Aborted:
                            {
                                text = "[#] " + t2.TrimStart(trimArray);
                                break;
                            }
                        default:
                            {
                                break;//非任务列表项，直接返回原文本。
                            }
                    }

                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 文档总标题，总是顶格
                var htmlDocumentTitle = GetDocumentTitle(text);
                if (htmlDocumentTitle != null)
                {
                    text = text.Trim(' ', '\t', '　');
                    if (text.ToLower().StartsWith("title>"))
                    {
                        text = "%" + text.Substring(6);
                    }
                    else if (text.StartsWith("标题>"))
                    {
                        text = "%" + text.Substring(3);
                    }

                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 作者信息，总是顶格
                if (text.StartsWith("@") || text.StartsWith("·"))
                {
                    text = text.TrimStart(' ', '\t', '　');
                    text = "@" + text.Substring(1).Trim();

                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 如果是水平线，总是顶格
                if (IsHorizontalLineText(text))
                {
                    orderListNumber = 0;

                    text = text.Replace("　", "").Replace("\t", "").Replace("－", "-");

                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 格式化等号组成的水平线
                //用以标志前一行（非空、非代码块）为3级标题（默认h3级，实际上只是个加粗段落 css class : p.-lme-title-3）
                if (IsHeaderMarkLine(text))
                {
                    text = text.Replace("　", "").Replace("\t", "").Replace("＝", "=");

                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 格式化块引用
                Regex regexBlockQuoter = new Regex(@"^[ 　]{0,3}([>》〉]{1}[ 　]{0,1}){1,}");
                var matchBlockQuoter = regexBlockQuoter.Match(text);
                if (matchBlockQuoter != null && matchBlockQuoter.Success)
                {
                    // orderListNumber = 0;  // 块引用不适合截断有序列表的计数。
                    var quoterHeader = matchBlockQuoter.Value.Replace("　", "").Replace("》", ">").Replace("〉", ">").Replace(">", "> ");
                    var newQuoterHeader = "";
                    for (int marki = 0; marki < quoterHeader.Replace(" ", "").Length; marki++)
                    {
                        newQuoterHeader += "> ";
                    }

                    //text = matchBlockQuoter.Value.Replace(" ", "").Replace("　", "").Replace("》", ">").Replace("〉", ">").Replace(">", "> ") +
                    text = newQuoterHeader + text.Substring(matchBlockQuoter.Length);
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 无序列表

                var regStarsStart = new Regex(@"^((([－\*＊\-\+＋][ 　\t]{1,}){1,})|(([－\-\+＋][ 　\t]{0,}){1,}))");
                var matchStarsStart = regStarsStart.Match(text);
                if (matchStarsStart.Success)
                {
                    orderListNumber = 0;

                    var prefixStars = new StringBuilder();
                    foreach (char c in matchStarsStart.Value)
                    {
                        if ("－-".Contains(c)) prefixStars.Append("- ");
                        else if ("＋+".Contains(c)) prefixStars.Append("+ ");
                        if ("*＊".Contains(c)) prefixStars.Append("* ");
                    }

                    text = prefixStars + text.Substring(matchStarsStart.Length);
                }

                #region 废弃代码
                //if (text.StartsWith("－") || text.StartsWith("-") || text.StartsWith("+"))
                //{
                //    //无序列表
                //    int index = -1;
                //    for (int j = 0; j < text.Length; j++)
                //    {
                //        char c = text[j];
                //        if (c != ' ' && c != '　' && c != '-' && c != '－' && c != '+' && c != '*')
                //        {
                //            index = j;
                //            break;
                //        }
                //    }

                //    if (index >= 0)
                //    {
                //        if (index >= text.Length) text = "";//不允许空无序列表存在
                //        else
                //        {
                //            text = text.Substring(0, index).Replace("-", "+").Replace("－", "+").Replace("　", "").Replace(" ", "").Replace("*", "+").Replace("+", "+ ") + text.Substring(index);
                //        }
                //    }
                //    else
                //    {
                //        if (IsHorizontalLineText(text) == false)//以-开头还可能是水平线，不能去除
                //        {
                //            text = "";
                //        }
                //        else
                //        {
                //            text = text.Replace("\t", "").Replace("－", "-").Replace("　", "").Replace(" ", "");
                //        }
                //    }
                //}
                //else if (text.StartsWith("* ") || text.StartsWith("*　") || text.StartsWith("*\t"))
                //{
                //*号开头的比较复杂，因为会和加粗、倾斜冲突——所以必须在星号后跟一个空格才算。

                ////仍然是无序列表
                //var anotherIndexOfStar = text.IndexOf("*", 2);
                //if (anotherIndexOfStar < 0)
                //{
                //    //如果此行中除开头外另存在一个星号，说明这应当作一个倾斜或加粗效果而不是无序列表。
                //    int index = -1;
                //    for (int j = 0; j < text.Length; j++)
                //    {
                //        char c = text[j];
                //        if (c != ' ' && c != '　' && c != '-' && c != '－' && c != '+' && c != '*')
                //        {
                //            index = j;
                //            break;
                //        }
                //    }

                //    if (index >= 0)
                //    {
                //        if (index >= text.Length) text = "";//不允许空无序列表存在
                //        else
                //        {
                //            text = text.Substring(0, index).Replace("-", "+").Replace("－", "+").Replace("　", "").Replace(" ", "").Replace("*", "+").Replace("+", "+ ") + text.Substring(index);
                //        }
                //    }
                //    else
                //    {
                //        text = "";
                //    }
                //}
                //else
                //{
                //    //解决星号开头的无序列表与倾斜效果的冲突
                //    //加粗是连续两个星号，不会被MarkdownSharp视为无序列表，不必考虑
                //    text = text.Substring(0, anotherIndexOfStar) + "</em>" + text.Substring(anotherIndexOfStar + 1);
                //    text = "<em>" + text.Substring(1);
                //}
                //}//else... 否则只是加粗或倾斜。一行文本开头就加粗或倾斜是完全有可能的。 
                #endregion

                #endregion

                #region 添加了单独的“表格格式化”功能，此处不需要再格式化，否则反而会冲突。
                //text = text.Replace("|", "｜");

                //if (text.Contains("|") || text.Contains("｜"))
                //{
                //    var lineContent = text.Trim(new char[] { ' ', '　', '\t' });
                //    if (lineContent.StartsWith("|") == false && lineContent.StartsWith("｜") == false)
                //    {
                //        text = "｜" + lineContent;
                //    }

                //    if (lineContent.EndsWith("|") == false && lineContent.EndsWith("｜") == false)
                //    {
                //        text += "｜";
                //    }
                //}

                //if (IsColumnDefinitionLine(text))
                //{
                //    text = text.Replace("：", ":").Replace("-", "－");
                //}
                #endregion

                #region 格式化方块区域和步骤标记文本
                Regex regexSquareStart = new Regex(@"^[ 　]{0,3}\[(?=([^\[\]].*$))");
                var startSquareMatch = regexSquareStart.Match(text);
                Regex regexStepLine = new Regex(@"^[ 　]{0,3}\[[ 　]{0,}(([sSｓＳ]([bBｂＢ]|[\-－—]|[tTｔＴ]|[pPｐＰ]|[eEｅＥ]))|([sSｓＳ]([tTｔＴ][eEｅＥ][pPｐＰ]){0,1}[ 　]{0,}(([bBｂＢ]([eEｅＥ][gGｇＧ][iIｉＩ][nNｎＮ]){0,1})|([eEｅＥ]([nNｎＮ][dDｄＤ]){0,1})){0,1}))\]");
                var matchStepText = regexStepLine.Match(text);
                Regex regexLink = new Regex(@"^\[.{1,}?\]\(.{0,}?\)");
                var matchLink = regexLink.Match(text);
                Regex regSquareStyleList = new Regex(@"(?<=(^\[[ 　\t]{0,}[\$￥])).*$");
                var matchSquareStyleList = regSquareStyleList.Match(text);
                if (matchSquareStyleList.Success)
                {
                    var styleList = matchSquareStyleList.Value.Trim(trimChars);
                    text = FormatSimpleStyleText(trimChars, styleList);
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                else if (startSquareMatch.Success && matchStepText.Success == false && matchLink.Success == false)
                {

                    orderListNumber = 0;

                    text = "[ " + text.Substring(startSquareMatch.Length).Trim(trimChars);
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                else
                {
                    Regex regexSquareEnd = new Regex(@"^[ 　]{0,3}\]");
                    var matchSquareEnd = regexSquareEnd.Match(text);
                    if (matchSquareEnd != null && matchSquareEnd.Success)
                    {
                        orderListNumber = 0;

                        text = "] " + text.Substring(matchSquareEnd.Length).Trim(trimChars);
                        sb.Append(text);
                        sb.Append(splitter);
                        continue;
                    }
                }
                #endregion

                #region 格式化步骤标记文本                
                if (matchStepText.Success)
                {
                    var tail = text.Substring(matchStepText.Length);
                    var header = matchStepText.Value.ToLower();
                    Regex endStep = new Regex(@"[eEｅＥ]([nNｎＮ][dDｄＤ]){0,1}\]");
                    if (endStep.Match(header).Success)
                    {
                        sb.Append("[SE] ");
                    }
                    else
                    {
                        Regex startStep = new Regex(@"[bBｂＢ]([eEｅＥ][gGｇＧ][iIｉＩ][nNｎＮ]){0,1}\]");
                        if (startStep.Match(header).Success)
                        {
                            sb.Append("[SB] ");
                        }
                        else sb.Append("[S-] ");
                    }
                    sb.Append(tail.Trim(new char[] { ' ', '　' }));
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 格式化 URL

                text = FormatUrls(text);
                #endregion

                #region 格式化自定义折叠区
                Regex regexStart = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?([！？!?ＩｉＷｗＥｅＱｑIiWwEeQq][ 　\t]*)?\{[\+\-＋－＝\=\*]{0,1}");
                var startMatch = regexStart.Match(text);
                if (startMatch.Success && text.Substring(startMatch.Length).IndexOf('}') < 0)    // 虽然现在支持单行折叠区，但却是在预处理时自动添加 \r\n 变成类似多行折叠区的形式的。
                {
                    orderListNumber = 0;
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                else
                {
                    Regex regexCustomRegionEnd = new Regex(@"^[ 　]{0,3}\}[ 　\t]*([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?");
                    var matchCustomRegionEnd = regexCustomRegionEnd.Match(text);
                    if (matchCustomRegionEnd != null && matchCustomRegionEnd.Success)
                    {
                        text = "} " + FormatRegionAlignText(text.Substring(matchCustomRegionEnd.Length));
                        sb.Append(text);
                        sb.Append(splitter);
                        continue;
                    }
                }
                #endregion

                #region 格式化 region 分割线
                if (IsRegionSplitter(text))
                {
                    orderListNumber = 0;

                    Regex regRegionSplitterStart = new Regex(@"^[ 　]{0,3}[.。]{3,}[ 　\t]{0,}[rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ]");
                    var matchRegionSplitterStart = regRegionSplitterStart.Match(text);
                    if (matchRegionSplitterStart != null && matchRegionSplitterStart.Success)
                    {
                        sb.Append("... region ");
                        var rSplitterContent = text.Substring(matchRegionSplitterStart.Length);
                        Regex regRegionSplitterEnd = new Regex(@"[.。]{3,}[ 　\t]{0,}$");
                        var matchRegionSplitterEnd = regRegionSplitterEnd.Match(rSplitterContent);
                        if (matchRegionSplitterEnd != null && matchRegionSplitterEnd.Success)
                        {
                            rSplitterContent = rSplitterContent.Substring(0, matchRegionSplitterEnd.Index);
                        }
                        sb.Append(rSplitterContent.Trim(new char[] { ' ', '　', '\t' }));
                        sb.Append(" ...");
                        sb.Append(splitter);
                        continue;
                    }
                }
                #endregion

                #region 格式化放大显示文本行

                //用一对尖括号括起数字字符开头的行表示该行文本应放大尖括号中数字指定的倍数。
                //注意：放大的倍数是应有限制。

                Regex regexMuiltyText = new Regex(@"^[ 　]{0,3}<[1234567890１２３４５６７８９０]{1,}([.。][1234567890１２３４５６７８９０]{1,}){0,}>");
                var matchMuiltyText = regexMuiltyText.Match(text);
                if (matchMuiltyText.Success)
                {
                    var tail = text.Substring(matchMuiltyText.Length);
                    var header = matchMuiltyText.Value.Replace("１", "1").Replace("２", "2").Replace("３", "3")
                        .Replace("４", "4").Replace("５", "5").Replace("６", "6").Replace("７", "7")
                        .Replace("８", "8").Replace("９", "9").Replace("０", "0").Replace("。", ".")
                        .Trim(new char[] { ' ', '　' });
                    sb.Append(header);
                    sb.Append(tail);
                    sb.Append(splitter);
                    continue;
                }

                #endregion

                #region 格式化布局行

                Regex regexLayoutLineText = new Regex(@"^[ 　]{0,3}\d{1,}(%|px)[;；]?[  \t]{1,}");
                var matchLayoutLineText = regexLayoutLineText.Match(text);
                if (matchLayoutLineText.Success)
                {
                    var header = matchLayoutLineText.Value.Trim(new char[] { ';', '；', ' ', '　', '\t', });
                    var tail = text.Substring(matchLayoutLineText.Length);
                    sb.Append(header);
                    sb.Append(" ");
                    sb.Append(tail);
                    sb.Append(splitter);
                    continue;
                }

                #endregion

                #region 格式化合并行文本
                Regex regexJoinUpLine = new Regex(@"^\^[ 　\t]{1,}");
                var matchJoinUp = regexJoinUpLine.Match(text);
                if (matchJoinUp.Success)
                {
                    var tail = text.Substring(matchJoinUp.Length);
                    sb.Append("^ ");
                    sb.Append(tail.Trim(new char[] { ' ', '　' }));
                    sb.Append(splitter);
                    continue;
                }
                #endregion

                #region 有序列表（这个要放到最后）

                Regex regexOrderedList = new Regex(@"^[ 　]{0,3}[0123456789０１２３４５６７８９]{1,}\.[ 　\t]*");
                var matchOrderedList = regexOrderedList.Match(text);
                if (matchOrderedList.Success)
                {
                    text = $"{++orderListNumber}. " + text.Substring(matchOrderedList.Length);
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }
                else
                {
                    // if (string.IsNullOrWhiteSpace(text) == false) orderListNumber = 0;
                    // 上面这个方案不好用。

                    // 有序列表的编号应该被这些元素“截断”。
                    // ①六级标题中的任意一级
                    // ②自定义折叠区或方块区域（包括自定义折叠区的分隔线）
                    // ③无序列表
                    // ④水平线
                    // 但如：块引用等元素不适合截断有序列表的数值计算。
                    // 所以，对有序列表项的处理要放在最后！！！
                }

                #endregion

                #region 中文五级序号自动格式化

                // 通常情况下，LME 支持自动编号（包括中文五级编号），可以由层级标题（以#开头）自动生成。
                // 但难保用户会复制些文本来使用，此时并不需要再手工改成层级标题。
                var zhTitleReg1 = new Regex(@"(?<=^[ 　]{0,3})[一二三四五六七八九十]{1,}(?=([ 　\t]{0,}?[\.．、]))");  // 中文一级标题
                var zhTitleMatch1 = zhTitleReg1.Match(text);
                if (zhTitleMatch1.Success)
                {
                    text = zhTitleMatch1.Value + "、" + text.Substring(zhTitleMatch1.Index + zhTitleMatch1.Length).TrimStart(new char[] { ' ', '　', '\t', '.', '．', '、', });
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }

                var zhTitleReg2 = new Regex(@"(?<=^[ 　]{0,3}[\(（][ 　\t]{0,}?)[一二三四五六七八九十]{1,}(?=([ 　\t]{0,}?[\)）]))");  // 中文二级标题
                var zhTitleMatch2 = zhTitleReg2.Match(text);
                if (zhTitleMatch2.Success)
                {
                    text = "（" + zhTitleMatch2.Value + "）" + text.Substring(zhTitleMatch2.Index + zhTitleMatch2.Length).TrimStart(new char[] { ' ', '　', '\t', ')', '）', });
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }

                var zhTitleReg3 = new Regex(@"(?<=^[ 　]{0,3})[0-9０-９]{1,}(?=([ 　\t]{0,}?[\.．、]))");  // 中文三级标题
                var zhTitleMatch3 = zhTitleReg3.Match(text);
                if (zhTitleMatch3.Success)
                {
                    text = FormatNumber(zhTitleMatch3.Value) + ". " + text.Substring(zhTitleMatch3.Index + zhTitleMatch3.Length).TrimStart(new char[] { ' ', '　', '\t', '.', '．', '、', });
                    // 用半角句点，是因为这个要考虑有序列表项。
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }

                var zhTitleReg4 = new Regex(@"(?<=^[ 　]{0,3}[\(（][ 　\t]{0,}?)[0-9０-９]{1,}(?=([ 　\t]{0,}?[\)）]))");  // 中文四级标题
                var zhTitleMatch4 = zhTitleReg4.Match(text);
                if (zhTitleMatch4.Success)
                {
                    text = "（" + FormatNumber(zhTitleMatch4.Value) + "）" + text.Substring(zhTitleMatch4.Index + zhTitleMatch4.Length).TrimStart(new char[] { ' ', '　', '\t', ')', '）', '.', '．', '、', });
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }

                var zhTitleRegC = new Regex(@"(?<=^[ 　]{0,3})[①-⑩]{1,}");  // 中文五级标题（公文实际上没有这一级）
                var zhTitleMatchC = zhTitleRegC.Match(text);
                if (zhTitleMatchC.Success)
                {
                    text = zhTitleMatchC.Value + " " + text.Substring(zhTitleMatchC.Index + zhTitleMatchC.Length).TrimStart(new char[] { ' ', '　', '\t', '.', '．', '、', });
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }

                var zhTitleRegR = new Regex(@"(?<=^[ 　]{0,3})[⑴-⒇]{1,}");  // 中文四级标题（小）
                var zhTitleMatchR = zhTitleRegR.Match(text);
                if (zhTitleMatchR.Success)
                {
                    text = "（" + FormatNumber(zhTitleMatchR.Value) + "）" + text.Substring(zhTitleMatchR.Index + zhTitleMatchR.Length).TrimStart(new char[] { ' ', '　', '\t', '.', '．', '、', });
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }

                var zhTitleRegD = new Regex(@"(?<=^[ 　]{0,3})[⒈-⒛]{1,}");  // 中文三级标题（小）
                var zhTitleMatchD = zhTitleRegD.Match(text);
                if (zhTitleMatchD.Success)
                {
                    text = FormatNumber(zhTitleMatchD.Value) + "．" + text.Substring(zhTitleMatchD.Index + zhTitleMatchD.Length).TrimStart(new char[] { ' ', '　', '\t', '.', '．', '、', });
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }

                var zhTitleRegE = new Regex(@"(?<=^[ 　]{0,3})[㈠-㈩]{1,}");  // 中文二级标题（小）
                var zhTitleMatchE = zhTitleRegE.Match(text);
                if (zhTitleMatchE.Success)
                {
                    text = "（" + FormatNumber(zhTitleMatchE.Value) + "）" + text.Substring(zhTitleMatchE.Index + zhTitleMatchE.Length).TrimStart(new char[] { ' ', '　', '\t', '.', '．', '、', });
                    sb.Append(text);
                    sb.Append(splitter);
                    continue;
                }

                #endregion

                text = FormatRadioAndCheckBoxElement(text);

                sb.Append(text);
                sb.Append(splitter);
            }

            // 追加处理有序列表项的处理。
            var newLines = sb.ToString().Split(new string[] { splitter, }, StringSplitOptions.None);
            var maxLength = 1;
            var regOrdListItem = new Regex(@"^\d{1,}?(?=\. )");
            for (int i = newLines.Length - 1; i >= 0; i--)
            {
                var line = newLines[i];
                var match = regOrdListItem.Match(line);
                if (match.Success)
                {
                    if (match.Length > maxLength)
                    {
                        maxLength = match.Length;
                    }

                    if (match.Length < maxLength)
                    {
                        newLines[i] = new string('0', maxLength - match.Length) + line;
                        // 注意，不能用：line = new string('0', maxLength - match.Length) + line;
                    }

                    if (match.Value == "1")
                    {
                        maxLength = 1;  // 重置
                        continue;
                    }
                }
            }

            var result = FormatMaterials(string.Join(splitter, newLines));
            return result;
        }

        /// <summary>
        /// 格式化 radio checkbox 元素。
        /// </summary>
        /// <param name=""></param>
        /// <returns></returns>
        public static string FormatRadioAndCheckBoxElement(string line)
        {
            if (string.IsNullOrWhiteSpace(line)) return "";

            var pieces = Utils.Text.SplitToPiecesByRegex(@"([\(（]{2}[-+＋－]?.*?[\)）]{2})|([\[【]{2}[-+＋－]?.*?[\]】]{2})", line);
            var regexRadioSpan = new Regex(@"(?<=([\(（]{2}))[-+＋－]?[ 　\t]{0,}.*?(?=([ 　\t]{0,}[\)）]{2}))");
            var regexCheckBoxSpan = new Regex(@"(?<=([\[【]{2}))[-+＋－]?[ 　\t]{0,}.*?(?=([ 　\t]{0,}[\]】]{2}))");
            var trimChars = new char[] { ' ', '　', '\t' };
            var sBuilder = new StringBuilder();
            foreach (var piece in pieces)
            {
                if (piece.IsMartchText == false)
                {
                    sBuilder.Append(piece.SourceText);
                    continue;
                }

                var matchRedio = regexRadioSpan.Match(piece.SourceText);
                if (matchRedio.Success)
                {
                    if (matchRedio.Value.StartsWith("+") || matchRedio.Value.StartsWith("-") ||
                        matchRedio.Value.StartsWith("＋") || matchRedio.Value.StartsWith("－"))
                    {
                        var alt = matchRedio.Value.Substring(1).Trim(trimChars);

                        if (matchRedio.Value.StartsWith("+") || matchRedio.Value.StartsWith("＋"))
                        {
                            sBuilder.Append($"((+ {alt}))");
                        }
                        else
                        {
                            sBuilder.Append($"((- {alt}))");
                        }
                        continue;
                    }
                    else
                    {
                        var alt = matchRedio.Value.Trim(trimChars);

                        sBuilder.Append($"(({alt}))");
                        continue;
                    }
                }

                var matchCheckBox = regexCheckBoxSpan.Match(piece.SourceText);
                if (matchCheckBox.Success)
                {
                    if (matchCheckBox.Value.StartsWith("+") || matchCheckBox.Value.StartsWith("-") ||
                        matchCheckBox.Value.StartsWith("＋") || matchCheckBox.Value.StartsWith("－"))
                    {
                        var alt = matchCheckBox.Value.Substring(1).Trim(trimChars);

                        if (matchCheckBox.Value.StartsWith("+") || matchRedio.Value.StartsWith("＋"))
                        {
                            sBuilder.Append($"[[+ {alt}]]");
                        }
                        else
                        {
                            sBuilder.Append($"[[- {alt}]]");
                        }
                        continue;
                    }
                    else
                    {
                        var alt = matchCheckBox.Value.Trim(trimChars);

                        sBuilder.Append($"[[{alt}]]");
                        continue;
                    }
                }

                sBuilder.Append(piece.SourceText);
            }

            return sBuilder.ToString();
        }

        public static string ReplaceMuiltyMarks(string srcText)
        {
            var regex = new Regex(@"[:：]{2,}");
            var result = regex.Replace(srcText, "：");

            regex = new Regex(@"[,，]{2,}");
            result = regex.Replace(result, "，");

            regex = new Regex(@"[。]{2,}");
            result = regex.Replace(result, "。");

            regex = new Regex(@"[?？]{2,}");
            result = regex.Replace(result, "？");

            regex = new Regex(@"[!！]{2,}");
            result = regex.Replace(result, "！");

            return result;
        }

        private static string FormatSimpleStyleText(char[] trimChars, string styleList, bool withStartBracket = true)
        {
            string text;
            var styles = styleList.Split(new char[] { ',', '，', ';', '；', });
            var sbStyles = new StringBuilder();
            foreach (var stylePiece in styles)
            {
                if (string.IsNullOrWhiteSpace(stylePiece))
                    continue;

                sbStyles.Append(stylePiece.Trim(trimChars));
                sbStyles.Append(", ");
            }

            if (withStartBracket)
            {
                text = "[ $ " + sbStyles.ToString();
            }
            else
            {
                text = "$ " + sbStyles.ToString();
            }
            return text;
        }

        /// <summary>
        /// 指定文本行是否引用块行。
        /// </summary>
        public static bool IsBlockQuoteLine(string lineText)
        {
            Regex regexBlockQuoter = new Regex(@"^[ 　]{0,3}([>》〉]{1}[ 　]{0,}){1,}.*$");
            var matchBlockQuoter = regexBlockQuoter.Match(lineText);
            if (matchBlockQuoter != null && matchBlockQuoter.Success)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 格式化自定义折叠区的标头文本。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static string FormatRegionStartHeader(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return "";

            var trimChars = new char[] { ' ', '　', '\t', };

            Regex regexStartI = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?([ＩｉIi][ 　\t]{0,})\{[\+\-＋－＝\=\*]{0,1}[ 　\t]{0,}");
            var matchStartI = regexStartI.Match(lineText);
            if (matchStartI != null && matchStartI.Success)
            {
                var header = matchStartI.Value.Trim(trimChars);
                if ("+＋".Contains(header[header.Length - 1]))
                {
                    return "I {+ " + FormatRegionAlignText(lineText.Substring(matchStartI.Length));
                }
                else if ("-－".Contains(header[header.Length - 1]))
                {
                    return "I {- " + FormatRegionAlignText(lineText.Substring(matchStartI.Length));
                }
                else return "I { " + FormatRegionAlignText(lineText.Substring(matchStartI.Length));
            }

            Regex regexStartQ = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?([qQｑＱ?？][ 　\t]{0,})\{[\+\-＋－＝\=\*]{0,1}[ 　\t]{0,}");
            var matchStartQ = regexStartQ.Match(lineText);
            if (matchStartQ != null && matchStartQ.Success)
            {
                var header = matchStartQ.Value.Trim(trimChars);
                if ("+＋".Contains(header[header.Length - 1]))
                {
                    return "? {+ " + FormatRegionAlignText(lineText.Substring(matchStartQ.Length));
                }
                else if ("-－".Contains(header[header.Length - 1]))
                {
                    return "? {- " + FormatRegionAlignText(lineText.Substring(matchStartQ.Length));
                }
                else return "? { " + FormatRegionAlignText(lineText.Substring(matchStartQ.Length));
            }

            Regex regexStartW = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?([wWｗＷ!！][ 　\t]{0,})\{[\+\-＋－＝\=\*]{0,1}[ 　\t]{0,}");
            var matchStartW = regexStartW.Match(lineText);
            if (matchStartW != null && matchStartW.Success)
            {
                var header = matchStartW.Value.Trim(trimChars);
                if ("+＋".Contains(header[header.Length - 1]))
                {
                    return "! {+ " + FormatRegionAlignText(lineText.Substring(matchStartW.Length));
                }
                else if ("-－".Contains(header[header.Length - 1]))
                {
                    return "! {- " + FormatRegionAlignText(lineText.Substring(matchStartW.Length));
                }
                else return "! { " + FormatRegionAlignText(lineText.Substring(matchStartW.Length));
            }

            Regex regexStartE = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?([eｅEＥ][ 　\t]{0,})\{[\+\-＋－＝\=\*]{0,1}[ 　\t]{0,}");
            var matchStartE = regexStartE.Match(lineText);
            if (matchStartE != null && matchStartE.Success)
            {
                var header = matchStartE.Value.Trim(trimChars);
                if ("+＋".Contains(header[header.Length - 1]))
                {
                    return "E {+ " + FormatRegionAlignText(lineText.Substring(matchStartE.Length));
                }
                else if ("-－".Contains(header[header.Length - 1]))
                {
                    return "E {- " + FormatRegionAlignText(lineText.Substring(matchStartE.Length));
                }
                else return "E { " + FormatRegionAlignText(lineText.Substring(matchStartE.Length));
            }

            Regex regexStart = new Regex(@"^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]{0,})?\{[\+\-＋－＝\=\*]{0,1}[ 　\t]{0,}");
            var matchStart = regexStart.Match(lineText);
            if (matchStart != null && matchStart.Success)
            {
                var header = matchStart.Value.Trim(trimChars);
                if ("+＋".Contains(header[header.Length - 1]))
                {
                    return "{+ " + FormatRegionAlignText(lineText.Substring(matchStart.Length));
                }
                else if ("-－".Contains(header[header.Length - 1]))
                {
                    return "{- " + FormatRegionAlignText(lineText.Substring(matchStart.Length));
                }
                else return "{ " + FormatRegionAlignText(lineText.Substring(matchStart.Length));
            }

            return FormatRegionAlignText(lineText);
        }

        /// <summary>
        /// 格式化自定义折叠区的标头文本、标尾文本的对齐符号（两侧冒号）。
        /// </summary>
        private static string FormatRegionAlignText(string src)
        {
            if (string.IsNullOrEmpty(src)) return "";

            var tmp = src.Trim(new char[] { ' ', '　', '\t', });

            bool left = false;
            bool right = false;
            if (tmp.StartsWith("：") || tmp.StartsWith(":"))
            {
                left = true;
            }

            if (tmp.EndsWith("：") || tmp.EndsWith(":"))
            {
                right = true;
            }

            tmp = tmp.Trim(new char[] { ' ', '　', '\t', '：', ':', });

            if (left) tmp = ": " + tmp;
            if (right) tmp += " :";
            return tmp;
        }

        /// <summary>
        /// 是否单独占一行的图像链接文本行。
        /// </summary>
        public static bool IsImageLinkLine(string text)
        {
            Regex regex = new Regex(@"^[ 　]{0,3}!\[[^\[\]]*?\]\([^\(\)]*?\)[ 　\t]{0,}$");
            var match = regex.Match(text);
            return (match != null && match.Success);
        }

        /// <summary>
        /// 是否单独占一行的图像链接文本行。如果是，将图像的 标题文本 通过 imgTtile 参数传出。
        /// </summary>
        public static bool IsImageLinkLine(string text, out string imgTitle)
        {
            Regex regex = new Regex(@"^[ 　]{0,3}!\[[^\[\]]*?\]\([^\(\)]*?\)[ 　\t]{0,}$");
            var match = regex.Match(text);

            if (match.Success)
            {
                Regex regexTitle = new Regex(@"(?<=(^[ 　]{0,3}!\[))[^\[\]]*?(?=(\]\([^\(\)]*?\)[ 　\t]{0,}$))");
                var matchTitle = regexTitle.Match(text);
                if (matchTitle.Success)
                {
                    imgTitle = matchTitle.Value;
                }
                else
                {
                    imgTitle = "";
                }
            }
            else
            {
                imgTitle = "";
            }

            return (match != null && match.Success);
        }

        public static bool IsImageLinkLine(string text, out string imgTitle, out string imgPath)
        {
            Regex regex = new Regex(@"(?<=(^[ 　]{0,3}!\[[^\[\]]*?\]\())[^\(\)]*?(?=([ 　\t]*(['""].*['""])?\)[ 　\t]{0,}$))");
            var match = regex.Match(text);

            if (match.Success)
            {
                Regex regexTitle = new Regex(@"(?<=(^[ 　]{0,3}!\[))[^\[\]]*?(?=(\]\([^\(\)]*?\)[ 　\t]{0,}$))");
                var matchTitle = regexTitle.Match(text);
                if (matchTitle.Success)
                {
                    imgTitle = matchTitle.Value;
                }
                else
                {
                    imgTitle = "";
                }
            }
            else
            {
                imgTitle = "";
            }

            if (match != null && match.Success)
            {
                imgPath = match.Value;
                return true;
            }
            else
            {
                imgPath = "";
                return false;
            }
        }

        /// <summary>
        /// 是否单独占一行的图像链接文本行。
        /// </summary>
        public static bool IsStartWithImageLink(string lineText)
        {
            Regex regex = new Regex(@"^[ 　]{0,3}!\[[^\[\]]*?\]\([^\(\)]*?\)");
            var match = regex.Match(lineText);
            return (match != null && match.Success);
        }

        /// <summary>
        /// ![word1](path word2)
        /// </summary>
        /// <param name="text"></param>
        /// <param name="word1"></param>
        /// <param name="path"></param>
        /// <param name="word2"></param>
        /// <returns></returns>
        public static bool IsImageGroupLinkLine(string text, out string word1, out string path, out string word2)
        {
            if (IsImageLinkLine(text) == false)
            {
                word1 = "";
                path = "";
                word2 = "";
                return false;
            }

            var regWord1 = new Regex(@"(?<=\[).*?(?=\])");
            var matchWord1 = regWord1.Match(text);
            if (matchWord1.Success)
            {
                word1 = matchWord1.Value;
            }
            else word1 = "";

            var regPath = new Regex(@"(?<=\().*?(?=(\)|[ \t].*\)))");
            var matchPath = regPath.Match(text);
            if (matchPath.Success)
            {
                path = matchPath.Value;
            }
            else path = "";

            var regWord2 = new Regex(@"(?<=[ \t]).*?(?=\)$)");
            var matchWord2 = regWord2.Match(text);
            if (matchWord2.Success)
            {
                word2 = matchWord2.Value;
            }
            else word2 = "";

            if (string.IsNullOrWhiteSpace(word2) == false)
            {
                var regStyle = new Regex(@"^style=['""].*['""]$");
                var matchStyle = regStyle.Match(word2);
                if (matchStyle.Success)
                {
                    word2 = word2.Replace("\"", "'");
                }
            }

            return true;
        }

        /// <summary>
        /// 是否以图像链接字符串开头。
        /// </summary>
        public static bool IsStartsWithImageLinkLine(string text)
        {
            Regex regex = new Regex(@"^[ 　]{0,3}!\[[^\[\]]*?\]\([^\(\)]*?\).*$");
            var match = regex.Match(text);
            return (match != null && match.Success);
        }

        /// <summary>
        /// 是否单独占一行的文件链接文本行。
        /// </summary>
        public static bool IsFileLinkLine(string text)
        {
            Regex regex = new Regex(@"^\[.*\]\(.*\).*$");
            var match = regex.Match(text);
            return (match != null && match.Success);
        }

        /// <summary>
        /// 用来提取文件链接中各个部分。文件链接形如：[word1](path word2)
        /// </summary>
        /// <param name="text"></param>
        /// <param name="word1"></param>
        /// <param name="path"></param>
        /// <param name="word2"></param>
        /// <returns></returns>
        public static bool IsFileLinkLine(string text, out string word1, out string path, out string word2)
        {
            if (IsFileLinkLine(text) == false)
            {
                word1 = "";
                path = "";
                word2 = "";
                return false;
            }

            var regWord1 = new Regex(@"(?<=\[).*?(?=\])");
            var matchWord1 = regWord1.Match(text);
            if (matchWord1.Success)
            {
                word1 = matchWord1.Value;
            }
            else word1 = "";

            var regPath = new Regex(@"(?<=\().*?(?=(\)|[ \t].*\)))");
            var matchPath = regPath.Match(text);
            if (matchPath.Success)
            {
                path = matchPath.Value;
            }
            else path = "";

            var regWord2 = new Regex(@"(?<=[ \t]).*?(?=\)$)");
            var matchWord2 = regWord2.Match(text);
            if (matchWord2.Success)
            {
                word2 = matchWord2.Value;
            }
            else word2 = "";

            if (string.IsNullOrWhiteSpace(word2) == false)
            {
                var regStyle = new Regex(@"^style=['""].*['""]$");
                var matchStyle = regStyle.Match(word2);
                if (matchStyle.Success)
                {
                    word2 = word2.Replace("\"", "'");
                }
            }

            return true;
        }

        /// <summary>
        /// 判断当前文本行是否树型文字表行。
        /// </summary>
        /// <param name="text">单行文本。</param>
        /// <param name="enableCommentTreeLine">是否支持以分号开头的、被注释掉的树型文字表。</param>
        /// <returns></returns>
        public static bool IsTreeListTextLine(string text, bool enableCommentTreeLine = false)
        {
            Regex rgx;
            if (enableCommentTreeLine)
            {
                rgx = new Regex(@"^[;；]?[!！][│　]{0,}[├└]{0,}(?!([ 　\t]*\{)).*$");
            }
            else
            {
                rgx = new Regex(@"^[!！][│　]{0,}[├└]{0,}(?!([ 　\t]*\{)).*$");
            }
            var match = rgx.Match(text);
            return (match.Success && IsStartsWithImageLinkLine(text) == false);
        }

        /// <summary>
        /// 此方法是 MarkdownEditorBase.IsTreeListLine() 方法的引用而已！
        /// </summary>
        /// <param name="line">要判断的文本行。</param>
        /// <param name="header">用于返回此文本行头部的格式字符串。</param>
        /// <param name="tail">用于返回此文本行尾部的正文字符串。</param>
        /// <param name="level">用于返回此文本行的层级，从1开始。当此层级为1时，文本行形如：！XXX。</param>
        /// <param name="startWithCommentMark">是否识别被分号注释掉的树型文字表。</param>
        public static bool IsTreeListTextLine(string line, out string header, out string tail, out int level, bool startWithCommentMark = false)
        {
            return MarkdownEditorBase.IsTreeListLine(line, out header, out tail, out level, startWithCommentMark);
        }

        /// <summary>
        /// 这个变量用于解决“步骤”的计数。当遇到 step end 标签的时候，此值归零。
        /// </summary>
        private static int stepBaseCount = 0;

        /// <summary>
        /// 在编译之前，移除一些不必编译到html文件中的行。这些行包括：
        /// ⑴文档总标题行——以%（或%）开头——即全、半角的百分号；
        ///     ★这个比较特殊，只有第一个以%（或%）开头的会被当作标题，其它以%（或%）开头的行会原样参加编译。
        /// ⑵注释行——以;（或；）开头——即全半角分号；
        ///     ☆所有以;（或；）开头的行都会被忽略。
        /// ⑶文档页眉——以~开头。
        /// </summary>
        public static string RemoveExtraLines(string srcText, string srcFileFullPath, bool ignoreNavAndSubLinks, out string htmlDocumentTitle,
            out string htmlDocumentPageHeader, out string htmlDocumentPageFooter, out bool compilePageMenu,
            out string headStyleTexts, out string bodyEndScripts, out List<string> importedCsses,
            out List<string> importedJavaScripts, out List<string> appendJaveScripts, out string subTitle,
            out string toWorkspacePrefix, bool forEpub, out string innerStyleText, out string testPaperId)
        {
            FileInfo fi = new FileInfo(srcFileFullPath);
            var wL = Globals.PathOfWorkspace.ToLower();
            var fL = fi.FullName.ToLower();
            var relativePath = "";
            toWorkspacePrefix = "";
            if (fL.StartsWith(wL))
            {
                relativePath = "~\\" + fL.Substring(wL.Length);
                toWorkspacePrefix = BuildPrefix(relativePath);
            }

            //Script 如果不处理，会被 MarkdownSharp 当成普通段落文本。
            String scripts;
            srcText = GetPageScripts(srcText, out scripts);
            bodyEndScripts = scripts;

            String styles;
            srcText = GetPageStyles(srcText, out styles);
            headStyleTexts = styles;

            //如果首行是任务列表（用来表示文档完成度），则移除首行。
            //2017年1月7日
            var regexOfLineEnd = new Regex(@"[\r\n]{1,}");
            var matchOfEnd = regexOfLineEnd.Match(srcText);
            if (matchOfEnd.Success && matchOfEnd.Index > 0)
            {
                var fstLine = srcText.Substring(0, matchOfEnd.Length);
                if (IsTaskLine(fstLine))
                {
                    srcText = srcText.Substring(matchOfEnd.Index + matchOfEnd.Length);
                }
            }

            htmlDocumentPageHeader = htmlDocumentTitle = htmlDocumentPageFooter = "";
            compilePageMenu = false;

            importedCsses = new List<string>();
            importedJavaScripts = new List<string>();
            appendJaveScripts = new List<string>();
            subTitle = null;
            innerStyleText = null;
            testPaperId = "";

            if (string.IsNullOrEmpty(srcText))
            {
                return string.Empty;
            }

            string[] lines;

            //尝试一下找“类型”，对填空题作些处理。
            //之前在做HistoryAssist的填空题时，一些格式与Markdown不兼容，这里处理一下就能用了。
            var startIndexOfType = srcText.IndexOf("类型＞＞");
            var endIndexOfType = srcText.IndexOf("＜＜类型");
            string typeText = "";
            if (startIndexOfType >= 0 && endIndexOfType >= 0 && endIndexOfType > startIndexOfType)
            {
                typeText = srcText.Substring(startIndexOfType + 4, endIndexOfType - startIndexOfType - 4);
            }

            if (typeText == "填空" || typeText == "填空题")
            {
                srcText = srcText.Replace("【", "`").Replace("】", "`")
                    .Replace("\n//", "\n;").Replace("\r//", "\n;");
            }

            lines = CustomMarkdownSupport.FormatMarkdownText(srcText).Replace("\r\n", "\n")
                .Split(new char[] { '\r', '\n' }, StringSplitOptions.None);//要保留空行


            #region 以惊叹号开头的行被视为树型文字表行，树型文字表以代码方式显示
            //要防止单独占一行的图像链接文本和以惊叹号开头的自定义折叠区。
            //注意：这个格式化不放在 FormatMarkdown()方法中。因为编译时才需要格式化，编辑时不需要格式化。
            for (int x = 0; x < lines.Length; x++)
            {
                if (IsTreeListTextLine(lines[x], out string header, out string tail, out int level))
                {
                    if (IsStartsWithImageLinkLine(lines[x])) continue;
                    string treeLineHeader;

                    if (header.Length > 1)
                    {
                        treeLineHeader = "！" + new string('　', header.Length - 1);
                    }
                    else { treeLineHeader = ""; }

                    var newLine = Regex.Replace(lines[x], @"\<br[ 　\t]*?/?\>", $"\n    {treeLineHeader}", RegexOptions.IgnoreCase);
                    lines[x] = "    " + newLine;
                }
            }

            // 必须重建 lines 数组
            lines = string.Join("\n", lines).Split(new char[] { '\n' }, StringSplitOptions.None);
            #endregion

            StringBuilder sb = new StringBuilder();
            bool removedDocumentTitleLine = false;
            bool removedSubTitleLine = false;
            bool removeFooterText = false;
            bool removePageHeader = false;

            //char[] trimChars = new char[] { '\t', ' ', '　' };
            //书写时文档标题、备注都必须严格的在行首添加标记字符——否则易与code块冲突。
            //实际编译时执行“三个空格以内皆有效”。

            var startLineIndex = 0;
            if (lines.Length > 0)
            {
                var lineFst = lines[0];
                if (IsTaskLine(lineFst) || IsDateLine(lineFst))
                {
                    //文档完成状态的标记行，不编译进Html。
                    startLineIndex = 1;//忽略第1行
                }
            }

            bool isInNavSquare = false;
            var trimChars = new char[] { ' ', '　', '\t', };

            var sbInnerStyleText = new StringBuilder();
            bool innerStyleIsOpen = false;

            for (int i = startLineIndex; i < lines.Length; i++)
            {
                string line = lines[i];

                if (StartsWithStyleHeaderTag(line.TrimEnd(), out string tailText1))  // 只 TrimEnd() 是因为要防止与行首有四个以上空格的代码块冲突
                {
                    if (innerStyleIsOpen)
                    {
                        // 如果之前有个 <Style> 开始标记，又碰到一个，自动添加结束标记并继续处理。
                        sbInnerStyleText.Append("</Style>");
                    }

                    if (EndsWithStyleCloseTag(tailText1, out string remainText))
                    {
                        // 如果是 <style>xxx</style> 这样的行
                        sbInnerStyleText.Append("<Style>" + remainText.Trim() + "</Style>");
                        continue;
                    }
                    else
                    {
                        // 如果只是 <style>xxx
                        sbInnerStyleText.Append("<Style>" + tailText1.Trim());
                        innerStyleIsOpen = true;
                        continue;
                    }
                }

                if (StartsWithStyleEnderTag(line.TrimEnd(), out string tailText2))
                {
                    if (innerStyleIsOpen)
                    {
                        // 碰到结束标记 </Style>
                        sbInnerStyleText.Append("</Style>");
                        line = tailText2;    // 本行剩下的文本继续处理。
                        innerStyleIsOpen = false;
                        //continue;
                    }
                    else
                    {
                        // 碰到意外的 </Style> 标记，前面没有 <Style> 标记，直接忽略
                        line = tailText2;    // 本行剩下的文本继续处理。
                        //continue;
                    }
                }
                else
                {
                    if (innerStyleIsOpen)
                    {
                        // 如果当前 innerStyleIsOpen 为真，则直接将当前行视为样式表行
                        if (line.StartsWith(".."))
                        {
                            sbInnerStyleText.Append(line.Substring(2).Trim(new char[] { ' ', '　', '\t', }));  // 注意，某些行会有 ..  .className 的写法，只能去除前面两个句点，后一个是不能去除的。
                        }
                        else
                        {
                            sbInnerStyleText.Append(line.Trim(new char[] { ' ', '　', '\t', }));
                        }
                        continue;
                    }
                    //else{...}   // 继续进行其它正常处理
                }

                // 副标题的处理要放在标题前面，因为副标题是以 %—— 开头的
                if (removedSubTitleLine == false)
                {
                    var subTitleText = GetSubTitle(line);
                    if (subTitleText != null)
                    {
                        subTitle = subTitleText;
                        removedSubTitleLine = true;//只有第一个以%开头的行才会被当作文档标题。
                        sb.Append("\n");
                        continue;
                    }
                }

                if (removedDocumentTitleLine == false)
                {
                    var title = GetDocumentTitle(line);
                    if (title != null)
                    {
                        htmlDocumentTitle = title;
                        removedDocumentTitleLine = true;//只有第一个以%开头的行才会被当作文档标题。
                        sb.Append("\n");
                        continue;
                    }
                }

                if (IsPageHeader(line))
                {
                    if (removePageHeader == false)
                    {
                        htmlDocumentPageHeader = line.Substring(1).Trim();
                        removePageHeader = true;//只有第一个以~开头的行才会被当作页眉。
                        sb.Append("\n");
                        continue;
                    }
                }
                else if (IsPageFoot(line))//即PageFooter
                {
                    if (removeFooterText == false)
                    {
                        htmlDocumentPageFooter = line.Substring(1);
                        removeFooterText = true;
                        sb.Append("\n");
                        continue;
                    }
                }
                else if (IsCommentLine(line) && IsCompileCommentLine(line) == false)
                {
                    if (IsTestPaperIdLine(line, out string tpid))
                    {
                        testPaperId = tpid;
                        sb.Append("\n");
                        continue;
                    }
                    else if (IsImageLinkComment(line, relativePath, toWorkspacePrefix, out string imgLink))
                    {
                        sb.Append(imgLink);  // 图像链接与下面两个不一样，直接添加在这里就可以了。
                    }
                    else if (IsImportCssComment(line, relativePath, toWorkspacePrefix, out string linkText))
                    {
                        // 添加外部 CSS 文件到 <head> 中
                        importedCsses.Add(linkText);  // 此方法调用结束后会添加到生成的 Html 的合适位置。
                    }
                    else if (IsAppendCssCodeComment(line, relativePath, toWorkspacePrefix, out string cssText))
                    {
                        importedCsses.Add(cssText);
                    }
                    else if (IsImportJavaScriptComment(line, relativePath, toWorkspacePrefix, out string scriptText, out bool beforeBody))
                    {
                        if (beforeBody)
                            importedJavaScripts.Add(scriptText);
                        else
                            appendJaveScripts.Add(scriptText);
                    }
                    else if (IsAppendJavaScriptCodeComment(line, relativePath, toWorkspacePrefix, out string appendScriptText, out bool beforeBody2))
                    {
                        if (beforeBody2)
                            importedJavaScripts.Add(appendScriptText);
                        else
                            appendJaveScripts.Add(appendScriptText);
                    }
                    else if (IsMenuMarkText(line))
                    {
                        compilePageMenu = true;
                    }
                    else if (IsAppendSingleAttributeMark(line, out bool? next, out string attributeText, out string tagName))
                    {
                        var directory = (next.HasValue && next.Value == false) ? "AddToPreview" : "AddToNext";  // 默认向后一个找到的 Html 标签中添加 ID
                        if (string.IsNullOrWhiteSpace(attributeText) == false)
                        {
                            sb.Append($"<br class='__custom-single-attribute-mark__' directory='{directory}' attributeText='{HttpUtility.HtmlAttributeEncode(attributeText)}' tagName='{(tagName == null ? "" : tagName)}'/>");
                        }
                    }
                    else if (IsAppendAttributesMark(line, out bool? classNext, out string atttributesText, out string attributesTagName, out bool forAllTags))
                    {
                        var directory = (classNext.HasValue && classNext.Value == false) ? "AddToPreview" : "AddToNext";  // 默认向后一个找到的 Html 标签中添加 ID
                        if (string.IsNullOrWhiteSpace(atttributesText) == false)
                        {
                            sb.Append($"<br class='__custom-attributes-mark__' directory='{directory}' attributesText='{HttpUtility.HtmlAttributeEncode(atttributesText)}' forAllTags='{forAllTags}' tagName='{(attributesTagName == null ? "" : attributesTagName)}'/>");
                        }
                    }
                    sb.Append("\n");
                    continue;
                    //以;（或；）号开头的行是注释行，不会被编译进html文档。
                }
                //任务列表原样输出！不再作为备注！2016年2月5日
                //else if (IsTaskLine(line))
                //{
                //    sb.Append("\n");
                //    continue;
                //    //以“[-]、[%]、[+]”开头的行也是注释，但是比较特殊，属于“任务列表”。
                //}
                else if (IsTableCaptionSplittor(line))
                {
                    //sb.Append("\n");//这个必须去除，否则表格的标题会与表格体分离——标题会成为一个单独的表格（只有一个单元格）
                    continue;
                }
                //下面这些也应去除。
                //＜＜＜信息＞
                // 标题＞＞明清君主专制制度的加强＜＜标题
                // 日期＞＞2013年4月14日＜＜日期
                // 作者＞＞杨震宇＜＜作者
                // 电邮＞＞historyassist@163.com＜＜电邮
                // 备注＞＞必修Ｉ，第一专题，第四节＜＜备注
                //＜信息＞＞＞
                else if (line.StartsWith("＜＜＜信息＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　类型＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　标题＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　日期＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　作者＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　辑录＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　电邮＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　备注＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("　电邮＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("＜信息＞＞＞"))
                {
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("[ 导航链接列表") && ignoreNavAndSubLinks == true)
                {
                    isInNavSquare = true;
                    sb.Append("\n"); continue;
                }
                else if (line.StartsWith("]") && ignoreNavAndSubLinks && isInNavSquare)
                {
                    isInNavSquare = false;
                    sb.Append("\n"); continue;
                }
                else if (ignoreNavAndSubLinks && isInNavSquare)
                {
                    continue;
                }
                else if (line.StartsWith("..") && IsRegionSplitter(line) == false)
                {
                    sb.Append(line.Substring(2).TrimStart(trimChars));
                    sb.Append("\n"); continue;
                }
                else
                {
                    //最后处理锚
                    //改造一下Markdown语法，
                    //按Markdown原始语法，[链接名](http://www.xxx.com)表示一个链接。
                    //但我这里的“锚”，会是这个样子[锚名](@锚ID 锚说明文本)
                    //锚名可以为空，锚ID必须以#号开头——否则它就只是一个普通的链接。
                    //锚ID不能包含空格，且最好是纯半角英文字母组成的。
                    //一个文档内的锚ID不能重复。

                    var text = line;
                    if (text.StartsWith("    ") || text.StartsWith("\t"))
                    {
                        sb.Append(line);
                        sb.Append("\n");
                        continue;
                    }
                }

                // 这里处理单选按钮与复选框
                line = CompileInputElement(line);

                sb.Append(line);
                sb.Append("\n");
            }

            innerStyleText = sbInnerStyleText.ToString();
            innerStyleText = innerStyleText.Replace("\r", "").Replace("\n", "");

            //尝试再找下标题
            if (string.IsNullOrEmpty(htmlDocumentTitle))
            {
                var startIndex = srcText.IndexOf("标题＞＞");
                var endIndex = srcText.IndexOf("＜＜标题");

                if (startIndex >= 0 && endIndex >= 0 && endIndex > startIndex)
                {
                    htmlDocumentTitle = srcText.Substring(startIndex + 4, endIndex - startIndex - 4);
                }
            }

            //尝试再找下脚注
            if (string.IsNullOrEmpty(htmlDocumentPageFooter))
            {
                //脚注一般用于显示作者
                var startIndex = srcText.IndexOf("作者＞＞");
                var endIndex = srcText.IndexOf("＜＜作者");

                if (startIndex >= 0 && endIndex >= 0 && endIndex > startIndex)
                {
                    htmlDocumentPageFooter = srcText.Substring(startIndex + 4, endIndex - startIndex - 4);
                }

                if (string.IsNullOrEmpty(htmlDocumentPageFooter))
                {
                    startIndex = srcText.IndexOf("辑录＞＞");
                    endIndex = srcText.IndexOf("＜＜辑录");
                    if (startIndex >= 0 && endIndex >= 0 && endIndex > startIndex)
                    {
                        htmlDocumentPageFooter = srcText.Substring(startIndex + 4, endIndex - startIndex - 4);
                    }
                }
            }

            #region 处理行内注释片段

            //行内注释片段，类似于 C 系列语言的多行注释，但在 LME 中不支持跨行。
            //这种注释主要用以插入一些特殊的替代标记，借以实现类似“全局环境变量”的功能。
            //例如：插入/*Title*/则自动转换为当前文档的标题文本、插入/*Path*/则自动转换当前文档的绝对路径、
            //      插入/*RelativePath*/则插入当前文档相对于工作区的路径（~/aaa/bbb/ddd/fff.md）、
            //      插入/*Nav*/则自动转换成类似层级列表的Action列表。
            //而且，还可以让用户自定义“全局环境变量”，并在编译前自动替换成用户定义文本。
            try
            {
                var dict = new Dictionary<string, string>();

                //添加内置的全局环境变量
                dict.Add("Title", htmlDocumentTitle);

                dict.Add("FileName", fi.Name);
                dict.Add("Path", fi.FullName);               //等价于 FullPath FullName
                dict.Add("FullName", fi.FullName);           //等价于 FullPath Path
                dict.Add("FullPath", fi.FullName);           //等价于 FullName Path

                dict.Add("DirectoryName", fi.Directory.Name);           //当前文档所在目录的短名
                dict.Add("DirectoryPath", fi.Directory.FullName);       //当前文档所在目录的物理路径
                dict.Add("DirectoryFullName", fi.Directory.FullName);   //当前文档所在目录的物理路径
                dict.Add("DirectoryFullPath", fi.Directory.FullName);   //当前文档所在目录的物理路径

                dict.Add("WorkspaceTitle", Globals.MainWindow.GetMetaFileTitleOfDirectory(Globals.PathOfWorkspace));   //当前工作区标题。
                dict.Add("WorkspaceName", new DirectoryInfo(Globals.PathOfWorkspace).Name);  //当前工作区目录短名
                dict.Add("WorkspacePath", Globals.PathOfWorkspace);      //当前工作区目录路径
                dict.Add("WorkspaceFullName", Globals.PathOfWorkspace);  //当前工作区目录路径
                dict.Add("WorkspaceFullPath", Globals.PathOfWorkspace);  //当前工作区目录路径

                //已移动到本方法头部。
                //var wL = Globals.PathOfWorkspace.ToLower();
                //var fL = fi.FullName.ToLower();
                if (fL.StartsWith(wL))
                {
                    //已移动到本方法头部。
                    //var relativePath = "~\\" + fL.Substring(wL.Length);
                    dict.Add("RelativePath", relativePath);  //相对于工作区的路径

                    //已移动到本方法头部。
                    //var toWorkspacePrefix = BuildPrefix(relativePath);
                    dict.Add(key: "~", toWorkspacePrefix);
                    dict.Add("ToWorkspace", toWorkspacePrefix);
                    dict.Add("ToWorkspacePath", toWorkspacePrefix);
                    dict.Add("PathToWorkspace", toWorkspacePrefix);
                }

                dict.Add("PageHeader", htmlDocumentPageHeader);
                dict.Add("PageFooter", htmlDocumentPageFooter);


                //工作区根目录元文件中，这个全局变量是没有意义的
                if (ignoreNavAndSubLinks == false)
                {
                    if (srcFileFullPath.ToLower() == Globals.MainWindow.GetMetaFilePathOfDirectory(Globals.PathOfWorkspace).ToLower())
                    {
                        dict.Add("Nav", "<span style=\"color:red;font-weight:bold;\">/\\*Nav-在此文件中无意义！\\*/</span>");
                    }
                    else
                    {
                        dict.Add("Nav", BuildNavigateActionsListText(srcFileFullPath, htmlDocumentTitle));
                    }
                }
                else
                {
                    if (forEpub == false)
                    {
                        dict.Add("Nav", "");
                    }
                }

                //如果当前文件是所在目录的元文件，
                //  则在此目录元文件中添加对“宿主目录的下级目录的元文件及宿主目录的下级 Markdown 文件”的链接的列表。
                //  此列表中不包括此目录元文件本身（因为无意义）！
                if (IsMetaFilePath(fi))
                {
                    //这个全局环境变量只在目录元文件中才有意义
                    var subLinksListHtml = BuildSubLinksListHtmlTextForMetaFile(srcFileFullPath, forEpub);
                    if (string.IsNullOrWhiteSpace(subLinksListHtml) == false && ignoreNavAndSubLinks == false)
                        dict.Add("SubLinksList", subLinksListHtml);
                    else
                    {
                        if (forEpub == false)
                        {
                            dict.Add("SubLinksList", "Hide");      // 避免没有新建下级文件（目录）时显示“未定义”的问题。
                                                                   // 这样会更自动化些。
                        }
                    }
                }

                //全局变量一共分三种：内置级（全局环境变量）、Windows用户级、工作区级。
                //其中内置的全局环境变量不能修改，其余两种由用户自己设置。
                var innerDict = new Dictionary<string, string>();
                foreach (var d in dict)
                {
                    innerDict.Add(d.Key, d.Value);
                }
                //以上为内置的全局环境变量

                //添加用户自定义环境变量
                foreach (var d in Globals.MainWindow.UserEnvironmentValues)
                {
                    //内置的环境变量不允许覆盖
                    if (innerDict.ContainsKey(d.Key) == false)
                    {
                        if (dict.ContainsKey(d.Key))            //如果用户定义了同名的，后一个有效。
                        {
                            dict[d.Key] = d.Value;
                        }
                        else { dict.Add(d.Key, d.Value); }
                    }
                }

                //添加工作区自定义环境变量
                foreach (var d in Globals.MainWindow.WorkspaceEnvironmentValues)
                {
                    //内置的环境变量不允许覆盖
                    if (innerDict.ContainsKey(d.Key) == false)
                    {
                        if (dict.ContainsKey(d.Key))
                        {
                            dict[d.Key] = d.Value;   //后定义的覆盖先定义同名环境变量；工作区中的覆盖Windows用户级的同名环境变量。
                        }
                        else { dict.Add(d.Key, d.Value); }
                    }
                }

                //必须再次循环才能解决行内注释片段
                var sb2 = new StringBuilder();
                var lines2 = sb.ToString().Split('\n');

                for (int i = 0; i < lines2.Length; i++)
                {
                    var line = lines2[i];
                    sb2.Append(FormatInlineEnviromentValueSpans(line, dict, forEpub));
                    sb2.Append("\n");
                }
                #endregion

                return sb2.ToString().Replace("\r", "");
            }
            catch (Exception ex)
            {
                LMessageBox.Show("未能处理行内注释片段，已忽略此步！异常消息如下：\r\n　　" + ex.Message,
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return srcText;
            }
        }

        private static bool IsTestPaperIdLine(string line, out string tpid)
        {
            var reg = new Regex(@"(?<=(^[;；][ 　\t]{0,}(TP|试卷)ID[ 　\t]{0,}[:：][ 　\t]{0,}))[^ 　\t]{1,}?(?=([ 　\t]{1.})|$)");
            var match = reg.Match(line);
            if (match.Success)
            {
                tpid = match.Value.Trim();
                return true;
            }

            tpid = "";
            return false;
        }

        /// <summary>
        /// 编译单选按钮或复选框。
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        private static string CompileInputElement(string line)
        {
            if (string.IsNullOrWhiteSpace(line)) return "";

            var pieces = Utils.Text.SplitToPiecesByRegex(@"([\(（]{2}[-+＋－]?.*?[\)）]{2})|([\[【]{2}[-+＋－]?.*?[\]】]{2})", line);
            var regexRadioSpan = new Regex(@"(?<=([\(（]{2}))[-+＋－]?[ 　\t]{0,}.*?(?=([ 　\t]{0,}[\)）]{2}))");
            var regexCheckBoxSpan = new Regex(@"(?<=([\[【]{2}))[-+＋－]?[ 　\t]{0,}.*?(?=([ 　\t]{0,}[\]】]{2}))");
            var trimChars = new char[] { ' ', '　', '\t' };
            var sBuilder = new StringBuilder();
            foreach (var piece in pieces)
            {
                if (piece.IsMartchText == false)
                {
                    sBuilder.Append(piece.SourceText);
                    continue;
                }

                var matchRedio = regexRadioSpan.Match(piece.SourceText);
                if (matchRedio.Success)
                {
                    if (matchRedio.Value.StartsWith("+") || matchRedio.Value.StartsWith("-") ||
                        matchRedio.Value.StartsWith("＋") || matchRedio.Value.StartsWith("－"))
                    {
                        var alt = matchRedio.Value.Substring(1).Trim(trimChars)
                            .Replace("\'", "&#39;").Replace("\"", "&quot;").Replace("&", "&amp;")
                            .Replace("<", "&lt;").Replace(">", "&gt;");

                        if (matchRedio.Value.StartsWith("+") || matchRedio.Value.StartsWith("＋"))
                        {
                            sBuilder.Append($"<span class='radio_wrap'><input class='radio' type='radio' checked='true' alt='{alt}'/>{alt}</span>");
                        }
                        else
                        {
                            sBuilder.Append($"<span class='radio_wrap'><input class='radio' type='radio' alt='{alt}'/>{alt}</span>");
                        }
                        continue;
                    }
                    else
                    {
                        var alt = matchRedio.Value.Trim(trimChars)
                            .Replace("\'", "&#39;").Replace("\"", "&quot;").Replace("&", "&amp;")
                            .Replace("<", "&lt;").Replace(">", "&gt;");

                        sBuilder.Append($"<span class='radio_wrap'><input class='radio' type='radio' alt='{alt}'/>{alt}</span>");
                        continue;
                    }
                }

                var matchCheckBox = regexCheckBoxSpan.Match(piece.SourceText);
                if (matchCheckBox.Success)
                {
                    if (matchCheckBox.Value.StartsWith("+") || matchCheckBox.Value.StartsWith("-") ||
                        matchCheckBox.Value.StartsWith("＋") || matchCheckBox.Value.StartsWith("－"))
                    {
                        var alt = matchCheckBox.Value.Substring(1).Trim(trimChars)
                           .Replace("\'", "&#39;").Replace("\"", "&quot;").Replace("&", "&amp;")
                           .Replace("<", "&lt;").Replace(">", "&gt;");

                        if (matchCheckBox.Value.StartsWith("+") || matchRedio.Value.StartsWith("＋"))
                        {
                            sBuilder.Append($"<span class='checkbox_wrap'><input class='checkbox' type='checkbox' checked='true' alt='{alt}'/>{alt}</span>");
                        }
                        else
                        {
                            sBuilder.Append($"<span class='checkbox_wrap'><input class='checkbox' type='checkbox' alt='{alt}'/>{alt}</span>");
                        }
                        continue;
                    }
                    else
                    {
                        var alt = matchCheckBox.Value.Trim(trimChars)
                           .Replace("\'", "&#39;").Replace("\"", "&quot;").Replace("&", "&amp;")
                           .Replace("<", "&lt;").Replace(">", "&gt;");

                        sBuilder.Append($"<span class='checkbox_wrap'><input class='checkbox' type='checkbox' alt='{alt}'/>{alt}</span>");
                        continue;
                    }
                }

                sBuilder.Append(piece.SourceText);
            }

            return sBuilder.ToString();
        }

        private static Regex regexOfStyleHeaderTag = null;

        private static bool StartsWithStyleHeaderTag(string lineText, out string tailText)
        {
            if (string.IsNullOrWhiteSpace(lineText))
            {
                tailText = null;
                return false;
            }

            if (regexOfStyleHeaderTag == null)
            {
                regexOfStyleHeaderTag = new Regex(@"^[\.]{2,2}[ 　\t]{0,}?<style>", RegexOptions.IgnoreCase);
            }

            var match = regexOfStyleHeaderTag.Match(lineText);
            if (match.Success)
            {
                tailText = lineText.Substring(match.Length);
                return true;
            }

            tailText = null;
            return false;
        }

        private static Regex regexOfStyleEnderTag = null;

        /// <summary>
        /// 这个是在一行开头写的结束标签 &lt;/style&gt;
        /// </summary>
        private static bool StartsWithStyleEnderTag(string lineText, out string tailText)
        {
            if (string.IsNullOrWhiteSpace(lineText))
            {
                tailText = null;
                return false;
            }

            if (regexOfStyleEnderTag == null)
            {
                regexOfStyleEnderTag = new Regex(@"^[\.]{2,2}[ 　\t]{0,}?</style>", RegexOptions.IgnoreCase);
            }

            var match = regexOfStyleEnderTag.Match(lineText);
            if (match.Success)
            {
                tailText = lineText.Substring(match.Length);
                return true;
            }

            tailText = null;
            return false;
        }

        private static Regex regexOfStyleCloseTag = null;

        /// <summary>
        /// 这个是在单行结尾处的 &lt;/style&gt;
        /// </summary>
        private static bool EndsWithStyleCloseTag(string lineText, out string tailText)
        {
            if (string.IsNullOrWhiteSpace(lineText))
            {
                tailText = null;
                return false;
            }

            if (regexOfStyleCloseTag == null)
            {
                regexOfStyleCloseTag = new Regex(@"</style>[ 　\t]{0,}?$");
                var options = regexOfStyleCloseTag.Options;
                options = options | RegexOptions.IgnoreCase;
            }

            var match = regexOfStyleCloseTag.Match(lineText);
            if (match.Success)
            {
                tailText = lineText.Substring(match.Length);
                return true;
            }

            tailText = null;
            return false;
        }

        private static bool IsImageLinkComment(string line, string relativePath, string toWorkspacePathPrefix, out string imgLink)
        {
            // 格式：
            // ; img : ~/Images~/abcd.png        some other comment chars
            // 路径有 3 种可能：
            //     ① 相对于工作区路径 ~/ 表示从工作区目录找到 Images~ 子目录下的 abcd.png 文件
            //     ② 以 ./ 或 .// 开头的路径，这个由用户计算层级，直接输出，不必考虑 Images~ 子目录下的 abcd.png 文件到底在哪里
            //     ③ 以 / 或字母开头的路径，表示从当前目录下的 Images~ 子目录中查找 abcd.png 文件，这个也直接输出

            var reg = new Regex(@"(?<=(^[;；][ 　\t]{0,}[iI][mM][gG][ 　\t]{0,}[:：][ 　\t]{0,}))[^ 　\t]{1,}?(?=([ 　\t]{1.})|$)");
            var match = reg.Match(line);
            if (match.Success)
            {
                var trimChars = new char[] { ' ', '　', '\t', '\'', '\"', };  // 防止路径两侧有多余的引号
                var path = match.Value.Replace("\\", "/").Trim(trimChars);
                if (path.StartsWith("~/"))
                {
                    imgLink = $"<img src=\'{toWorkspacePathPrefix + path.Substring(2)}\'/>";
                }
                else
                {
                    imgLink = $"<img src=\'{path}\'/>";
                }
                return true;
            }

            imgLink = "";
            return false;
        }

        private static bool IsImportCssComment(string line, string relativePath, string toWorkspacePathPrefix, out string linkText)
        {
            // 格式：
            // ; css : < ~/_Styles~/abcd.css        some other comment chars
            var reg = new Regex(@"(?<=(^[;；][ 　\t]{0,}[cC][sS][sS][ 　\t]{0,}[:：][ 　\t]{0,}))[^ 　\t]{1,}?(?=([ 　\t]{1.})|$)");
            var match = reg.Match(line);
            if (match.Success)
            {
                var trimChars = new char[] { ' ', '　', '\t', '\'', '\"', };
                var path = match.Value.Replace("\\", "/").Trim(trimChars);  // 防止路径两侧有多余的引号
                if (path.StartsWith("~/"))
                {
                    //<link id='red_table_style' rel="stylesheet" href="/*PathToWorkspace*/_Styles~/my-style.css" type="text/css">
                    linkText = $"<link type=\"text/css\" rel=\"stylesheet\" href=\"{toWorkspacePathPrefix + path.Substring(2)}\"/>";
                }
                else
                {
                    linkText = $"<link type=\"text/css\" rel=\"stylesheet\" href=\"{path}\"/>";
                }
                return true;
            }

            linkText = "";
            return false;
        }

        private static bool IsAppendCssCodeComment(string line, string relativePath, string toWorkspacePathPrefix, out string cssText)
        {
            // 格式：
            // ; css :: < .xxx{color:red;}        some other comment chars
            var reg = new Regex(@"(?<=(^[;；][ 　\t]{0,}[cC][sS][sS][ 　\t]{0,}[ 　\t]{0,}[:：]{2,}[ 　\t]{0,}))[^ 　\t]{1,}.*$");
            var match = reg.Match(line);
            if (match.Success)
            {
                var trimChars = new char[] { ' ', '　', '\t', };
                var text = match.Value.Replace("\\", "/").Trim(trimChars);

                text = text.Trim(trimChars);
                cssText = $"<style>{text.Trim(trimChars)}</style>";

                return true;
            }

            cssText = "";
            return false;
        }

        /// <summary>
        /// 引入 javascript(js)脚本。——只写一个冒号。
        /// </summary>
        /// <param name="line"></param>
        /// <param name="relativePath"></param>
        /// <param name="toWorkspacePathPrefix"></param>
        /// <param name="scriptText"></param>
        /// <param name="beforeBody"></param>
        /// <returns></returns>
        private static bool IsImportJavaScriptComment(string line, string relativePath, string toWorkspacePathPrefix, out string scriptText, out bool beforeBody)
        {
            // 格式：
            // ; js : < ~/_Scripts~/abcd.js        some other comment chars
            var reg = new Regex(@"(?<=(^[;；][ 　\t]{0,}(([jJ][sS])|(([jJ][aA][vV][aA])?[sS][cC][rR][iI][pP][tT]))[ 　\t]{0,}[:：][ 　\t]{0,}))([<>]{1,}[ 　\t]{1,})?[^ 　\t]{1,}?(?=([ 　\t]{1.})|$)");
            var match = reg.Match(line);
            if (match.Success)
            {
                var trimChars = new char[] { ' ', '　', '\t', };
                var path = match.Value.Replace("\\", "/").Trim(trimChars);

                var trimChars2 = new char[] { ' ', '　', '\t', '<', '>', };
                if (path.StartsWith("<"))
                {
                    path = path.Trim(trimChars2);
                    beforeBody = true;
                }
                else
                {
                    if (path.StartsWith(">"))
                    {
                        path = path.Trim(trimChars2);
                    }
                    beforeBody = false;  // 默认加到 </body> 防止因元素或库未载入而出错。
                }

                if (path.StartsWith("~/"))
                {
                    var trimChars3 = new char[] { ' ', '　', '\t', '\'', '\"', };  // 防止路径两侧有多余的引号
                    path = path.Trim(trimChars3);
                    //<link id='red_table_style' rel="stylesheet" href="/*PathToWorkspace*/_Styles~/my-style.css" type="text/css">
                    scriptText = $"<script src=\"{toWorkspacePathPrefix + path.Substring(2)}\"></script>";
                }
                else
                {
                    scriptText = $"<script src=\"{path}\"></script>";
                }
                return true;
            }

            scriptText = "";
            beforeBody = true;
            return false;
        }

        /// <summary>
        /// 这种是直接写 JavaScript(JS)脚本。——要写两个以上冒号。
        /// </summary>
        /// <param name="line"></param>
        /// <param name="relativePath"></param>
        /// <param name="toWorkspacePathPrefix"></param>
        /// <param name="scriptText"></param>
        /// <param name="beforeBody"></param>
        /// <returns></returns>
        private static bool IsAppendJavaScriptCodeComment(string line, string relativePath, string toWorkspacePathPrefix, out string scriptText, out bool beforeBody)
        {
            // 格式：
            // ; js :: < ~/_Scripts~/abcd.js        some other comment chars
            var reg = new Regex(@"(?<=(^[;；][ 　\t]{0,}(([jJ][sS])|(([jJ][aA][vV][aA])?[sS][cC][rR][iI][pP][tT]))[ 　\t]{0,}[:：]{2,}[ 　\t]{0,}))([<>]{1,}[ 　\t]{1,})?[^ 　\t]{1,}.*$");
            var match = reg.Match(line);
            if (match.Success)
            {
                var trimChars = new char[] { ' ', '　', '\t', };
                var path = match.Value.Replace("\\", "/").Trim(trimChars);

                var trimChars2 = new char[] { ' ', '　', '\t', '<', '>', };
                if (path.StartsWith("<"))
                {
                    path = path.Trim(trimChars2);
                    beforeBody = true;
                }
                else
                {
                    if (path.StartsWith(">"))
                    {
                        path = path.Trim(trimChars2);
                    }
                    beforeBody = false;  // 默认加到 </body> 防止因元素或库未载入而出错。
                }

                scriptText = $"<script>{path.Trim(trimChars)}</script>";

                return true;
            }

            scriptText = "";
            beforeBody = true;
            return false;
        }

        /// <summary>
        /// 根据当前文档向工作区的相对短路径来生成路径前缀，以便引用工作区目录下的某个外部文件。
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private static string BuildPrefix(string path)
        {
            if (string.IsNullOrWhiteSpace(path)) return "";
            if (path.StartsWith("~\\")) path = path.Substring(2);

            var text = "";
            foreach (char c in path)
            {
                if (c == '\\') text += "../";
            }

            if (text.Length <= 0) text = "./";

            return text;
        }

        /// <summary>
        /// 是否附加指示 ID 标记。这类标记用于向所在位置的前部html标记头或后部html头添加一个 id 标识符——以便编写自定义CSS来进行修饰。
        /// 如 &lt;quoteblock&gt; 之类的对象，MarkdownSharp 在进行编译时是不会加个 id 的。现在用这个办法来变通地实现——可以省去直接写 Html 的麻烦。
        /// </summary>
        /// <param name="line"></param>
        /// <param name="next"></param>
        /// <param name="attributeText"></param>
        /// <param name="tagName"></param>
        /// <returns></returns>
        private static bool IsAppendSingleAttributeMark(string line, out bool? next, out string attributeText, out string tagName)
        {
            if (string.IsNullOrWhiteSpace(line))
            {
                next = null;
                attributeText = null;
                tagName = null;
                return false;
            }

            Regex regex = new Regex(@"^[ 　]{0,3}[;；][ 　\t]{0,}[<>]{0,}[ 　\t]{0,}?[\(（]@([a-zA-Z][a-zA-Z0-9\-_]{0,}:)?[a-zA-Z][a-zA-Z0-9\-_]{0,}([ 　\t]{1,}.*)[\)）].*$");
            var match = regex.Match(line);
            if (match.Success)
            {
                var nextOrPreviewMark = new Regex(@"(?<=(^[ 　]{0,3}[;；][ 　\t]{0,}))[<>]{1,}(?=([ 　\t]{0,}[\(（]@))");
                var mNext = nextOrPreviewMark.Match(line);
                if (mNext.Success)
                {
                    var nextText = mNext.Value.Trim(new char[] { ' ', '　', '\t', });
                    next = nextText.StartsWith(">");
                }
                else next = true;  // 默认是 > 表示向下添加 ID

                var attributeMark = new Regex(@"(?<=([\(（]@))([a-zA-Z][a-zA-Z0-9\-_]{0,}:)?[a-zA-Z][a-zA-Z0-9\-_]{0,}");
                var mAttribute = attributeMark.Match(line);
                if (mAttribute.Success)
                {
                    attributeText = mAttribute.Value;
                }
                else attributeText = null;

                var tagNameReg = new Regex(@"(?<=([\(（]@([a-zA-Z][a-zA-Z0-9\-_]{0,}:)?[a-zA-Z][a-zA-Z0-9\-_]{0,}[ 　\t]{1,})).*(?=([ 　\t]{0,}[\)）]))");
                var mTagName = tagNameReg.Match(line);
                if (mTagName.Success)
                {
                    tagName = mTagName.Value;
                }
                else tagName = null;

                if (string.IsNullOrWhiteSpace(attributeText))
                {
                    attributeText = null;
                    tagName = null;
                    return false;
                }

                return true;
            }
            else
            {
                next = null;
                attributeText = null;
                tagName = null;
                return false;
            }
        }

        /// <summary>
        /// 格式：  ；> (@attribute-name:attribute-value target-element-name)
        /// 是否向 html 元素批量写入 attribute 属性值。这类标记用于向所在位置的前部html标记头或后部html头添加一个 id 标识符——以便编写自定义CSS来进行修饰。
        /// 如  &lt;quoteblock&gt; 之类的对象，MarkdownSharp 在进行编译时是不会加个 id 的。现在用这个办法来变通地实现——可以省去直接写 Html 的麻烦。
        /// </summary>
        /// <param name="line"></param>
        /// <param name="next"></param>
        /// <param name="attributesText"></param>
        /// <param name="tagName"></param>
        /// <returns></returns>
        private static bool IsAppendAttributesMark(string line, out bool? next, out string attributesText, out string tagName, out bool forAllTags)
        {
            if (string.IsNullOrWhiteSpace(line))
            {
                next = null;
                attributesText = null;
                tagName = null;
                forAllTags = true;
                return false;
            }

            // 整理注释行，提高写法对全半角空格、Tab 符的兼容性
            line = Regex.Replace(line, @"[ 　\t]{0,}[,，][ 　\t]{0,}", ",");
            line = Regex.Replace(line, @"[ 　\t]{0,}[:：][ 　\t]{0,}", ":");
            line = Regex.Replace(line, @"[ 　\t]{0,}[@][ 　\t]{0,}", "@");
            line = Regex.Replace(line, @"[ 　\t]{0,}[&][ 　\t]{0,}", "&");
            line = Regex.Replace(line, @"[ 　\t]{1,}", " ");

            Regex regex = new Regex(@"^[ 　]{0,3}[;；][ 　\t]{0,}[<>]{0,}[ 　\t]{0,}?[\(（]&([a-zA-Z][a-zA-Z0-9\-_]{0,}[ 　\t]*?[:：][ 　\t]*?)?([a-zA-Z]{1,}[a-zA-Z0-9\-_]{0,})([,，][a-zA-Z]{1,}[a-zA-Z0-9\-_]{0,}){0,}?([ 　\t]{1,}.*)[\)）].*$");
            var match = regex.Match(line);
            if (match.Success)
            {
                var nextOrPreviewMark = new Regex(@"(?<=(^[ 　]{0,3}[;；][ 　\t]{0,}))[<>]{1,}(?=([ 　\t]{0,}[\(（]&))");
                var mNext = nextOrPreviewMark.Match(line);
                if (mNext.Success)
                {
                    var nextText = mNext.Value.Trim(new char[] { ' ', '　', '\t', });
                    next = nextText.StartsWith(">");

                    if (nextText.Length > 1)
                    {
                        var count = 1;
                        var startChar = nextText[0];
                        if (startChar != '>') startChar = '<';
                        for (int x = 1; x < nextText.Length; x++)
                        {
                            if (nextText[x] == startChar) count++;
                            if (count >= 2)
                            {
                                break;
                            }
                        }

                        forAllTags = count > 1;
                    }
                    else
                    {
                        forAllTags = false;
                    }
                }
                else
                {
                    next = true;  // 默认是 > 表示向下添加 class
                    forAllTags = true;
                }

                var attributesTextMark = new Regex(@"(?<=([\(（]&))([a-zA-Z][a-zA-Z0-9\-_]{0,}[ 　\t]*?[:：][ 　\t]*?)?([a-zA-Z]{1,}[a-zA-Z0-9\-_]{0,})([,，][a-zA-Z]{1,}[a-zA-Z0-9\-_]{0,}){0,}?(?=[ \t　])");
                var mAttributes = attributesTextMark.Match(line);
                if (mAttributes.Success)
                {
                    attributesText = mAttributes.Value;
                    if (attributesText.Contains(',') || attributesText.Contains('，'))
                    {
                        var spans = attributesText.Split(new char[] { ',', '，', }, StringSplitOptions.RemoveEmptyEntries);
                        if (spans.Length <= 0)
                        {
                            attributesText = null;
                        }
                        else
                        {
                            attributesText = string.Join(" ", spans);
                        }
                    }
                }
                else attributesText = null;

                var tagNameReg = new Regex(@"(?<=([\(（]&([a-zA-Z][a-zA-Z0-9\-_]{0,}[ 　\t]*?[:：][ 　\t]*?)?([a-zA-Z]{1,}[a-zA-Z0-9\-_]{0,}([,，][a-zA-Z]{1,}[a-zA-Z0-9\-_]{0,}){0,}?[ 　\t]{1,}))).*(?=([ 　\t]{0,}[\)）][ 　\t]{0,}$))");
                var mTagName = tagNameReg.Match(line);
                if (mTagName.Success)
                {
                    tagName = mTagName.Value;
                }
                else tagName = null;

                if (string.IsNullOrWhiteSpace(attributesText))
                {
                    attributesText = null;
                    tagName = null;
                    forAllTags = true;
                    return false;
                }

                return true;
            }
            else
            {
                next = null;
                attributesText = null;
                tagName = null;
                forAllTags = true;
                return false;
            }
        }

        private static string CompileUrls(string line)
        {
            if (string.IsNullOrWhiteSpace(line)) return "";
            var sb = new StringBuilder();
            var pieces = Utils.Text.SplitToPiecesByRegex("\\[.*\\][\\(（]\\@{0,1}[a-zA-z][a-zA-z0-9\\-_]*?([ 　\\t]{1,}\\^{0,1}[\"“”].*[\"“”])?[\\)）]", line);
            if (pieces == null || pieces.Count <= 0) return line;

            foreach (var piece in pieces)
            {
                if (piece.IsMartchText == false)
                {
                    sb.Append(piece.SourceText);
                    continue;
                }
                else
                {
                    bool isFootComment = false;
                    var regFootComment = new Regex("^[ 　]{0,3}\\[.*\\][\\(（]\\@[a-zA-z][a-zA-z0-9\\-_]*([ 　\\t]{1,}\\^[\"“”].*?[\"“”])?[\\)）]");
                    if (regFootComment.Match(piece.SourceText).Success) isFootComment = true;

                    var url = FormatUrl(piece.SourceText, out string title, out string idOrUrlPah,
                        out string alt, out bool isAnchor, out bool isSupper);
                    string compiledUrl;
                    if (isAnchor)
                    {
                        if (isFootComment)
                        {
                            if (isSupper)
                            {
                                compiledUrl = $"<span id=\"{idOrUrlPah}\" class=\"anchor\" alt=\"{alt}\">{title}</span> {alt}";  // $"<span id=\"{idOrUrlPah}\" alt=\"{alt}\">{title}</span> {alt}";
                            }
                            else
                                compiledUrl = $"<span id=\"{idOrUrlPah}\" class=\"anchor\" alt=\"{alt}\">{title}</span>";  // $"<span id=\"{idOrUrlPah}\" alt=\"{alt}\">{title}</span>";
                        }
                        else
                            compiledUrl = $"[{title}](@{idOrUrlPah} \"{alt}\")";  // $"<span id=\"{idOrUrlPah}\" alt=\"{alt}\">{title}</span>";
                    }
                    else  // url
                    {
                        // 如果只是普通 Url ，简单地格式化处理一下，然后还是交 MarkdownSharp 处理。
                        // 这种情况下，^ 不起作用的。
                        compiledUrl = $"[{title}]({idOrUrlPah} \"{alt}\")";   // $"<a href=\"{idOrUrlPah}\" alt=\"{alt}\">{title}</span>";
                    }
                    sb.Append(compiledUrl);
                    continue;
                }
            }

            return sb.ToString();
        }

        private static string FormatUrls(string line)
        {
            if (string.IsNullOrWhiteSpace(line)) return "";
            var sb = new StringBuilder();
            var pieces = Utils.Text.SplitToPiecesByRegex("\\[[^\\[\\]]*\\][\\(（]\\@{0,1}[a-zA-z][a-zA-z0-9\\-_]*[ 　\\t]{1,}[\"“”].*[\"“”][\\)）]", line);
            if (pieces == null || pieces.Count <= 0) return line;

            foreach (var piece in pieces)
            {
                if (piece.IsMartchText == false)
                {
                    sb.Append(piece.SourceText);
                    continue;
                }
                else
                {
                    sb.Append(FormatUrl(piece.SourceText, out string title, out string id,
                        out string alt, out bool isAnchor, out bool isSupper));
                    continue;
                }
            }

            return sb.ToString();
        }

        private static string FormatUrl(string sourceText, out string title, out string id, out string alt,
            out bool isAnchor, out bool isSupper)
        {
            title = "";
            alt = "";
            id = "";
            isAnchor = false;
            isSupper = false;

            if (string.IsNullOrWhiteSpace(sourceText)) return "";

            Regex regTitle = new Regex("(?<=\\[).*(?=(\\][\\(（]\\@{0,1}))");
            var matchTitle = regTitle.Match(sourceText);
            if (matchTitle.Success) title = matchTitle.Value;

            Regex regId = new Regex("(?<=(\\][\\(（]\\@{0,1}))[a-zA-Z][a-zA-Z0-9\\-_]*(?=([ 　\t]|[\\)）]))");
            var matchId = regId.Match(sourceText);
            if (matchId.Success) id = matchId.Value;

            Regex regAlt = new Regex("(?<=([ 　\\t]\\^{0,1}[\"])).*?(?=([\"][\\)）]))");
            var matchAlt = regAlt.Match(sourceText);
            if (matchAlt.Success) alt = matchAlt.Value;

            var anchorMarkReg = new Regex("\\][\\(（]\\@");
            var matchAnchorMark = anchorMarkReg.Match(sourceText);
            isAnchor = matchAnchorMark.Success;

            var regIsSupperMark = new Regex("\\^\".*?\"[ 　\\t]*?[\\)）]");
            if (regIsSupperMark.Match(sourceText).Success) isSupper = true;

            if (string.IsNullOrWhiteSpace(alt))
            {
                if (matchAnchorMark.Success)
                {
                    return $"[{title}](@{id})";
                }
                else
                    return $"[{title}]({id})";
            }
            else
            {
                if (matchAnchorMark.Success)
                {
                    return $"[{title}](@{id} {(isSupper ? "^" : "")}\"{alt}\")";
                }
                else
                    return $"[{title}]({id} \"{alt}\")";
            }
        }

        /// <summary>
        /// 目录元文件总是在该目录下，且短名由“_目录名.md”构成。
        /// </summary>
        public static bool IsMetaFilePath(FileInfo fi)
        {
            if (fi == null || fi.Exists == false) return false;
            if (fi.Name.StartsWith("_") == false) return false;

            var lowerFiName = fi.Name.ToLower();
            if (lowerFiName == "_" + fi.Directory.Name.ToLower() + ".md") return true;

            if (lowerFiName.StartsWith("_" + fi.Directory.Name.ToLower() + "_tmp") && lowerFiName.EndsWith("~.md")) return true;
            //加这个是为了在演示时也避免 /*SubLinks*/ 环境变量仍然出现不必要的提示。
            //演示时，目录元文件编译的 Html 文件名是以 _目录名_tmp01~.html 的形式出现的。
            //如果不是分片演示，则没有数字。

            return false;
        }

        private static string BuildSubLinksListHtmlTextForMetaFile(string srcFileFullPath, bool forEpub = false)
        {
            if (forEpub) return "";

            var wtvi = Globals.MainWindow.FindWorkspaceTreeViewItem(srcFileFullPath);
            if (wtvi == null || wtvi.Items.Count <= 0) return "";

            var sb = new StringBuilder();
            int count = 0;
            foreach (var i in wtvi.Items)
            {
                var item = i as WorkspaceTreeViewItem;
                if (item == null || item.IsAborted) continue;
                switch (item.ItemType)
                {
                    case WorkspaceTreeViewItem.Type.File:
                        {
                            string shortName = item.Title;
                            var linkText = Globals.MainWindow.BuildLinkText(item.FullPath, srcFileFullPath, ref shortName, true);
                            sb.Append("\n");
                            sb.Append($"<p class=\"sub-links-paragraph\">&nbsp;&nbsp;&nbsp;&nbsp;<a class=\"sub-links-action\" href=\"{linkText}\">{item.Title}</a></p>");
                            //&emsp;是全角空格，宽度是1em；-实测，IE不支持！
                            sb.Append("\n");
                            count++;
                            break;
                        }
                    case WorkspaceTreeViewItem.Type.Folder:
                        {
                            string shortName = item.Title;
                            var linkText = Globals.MainWindow.BuildLinkText(item.MetaFilePath, srcFileFullPath, ref shortName, true);
                            sb.Append("\n");
                            sb.Append($"<p class=\"sub-links-paragraph\">&nbsp;&gt;&gt;&nbsp;<a class=\"sub-links-action\" href=\"{linkText}\">" + item.Title + "</a></p>");
                            //&emsp;是全角空格，宽度是1em；&ensp;是半角空格，即0.5em宽。-实测，IE不支持！
                            sb.Append("\n");
                            count++;
                            break;
                        }
                    default:
                        continue;
                }
            }

            var prefix = count > 0 ? "<hr class='SubLinksListLine'/>\n<p class=\"sub-links-list-header\">请参阅：</p>\n" : "";

            return $"\n<div class=\"sub-links-list\" style=\"text-indent:0px;\">{prefix}\n{sb.ToString()}\n</div>\n";
        }

        /// <summary>
        /// 创建 /*Nav*/全局环境变量生成的导航路径列表的 Html 文本。
        /// </summary>
        /// <param name="srcFileFullPath"></param>
        /// <returns></returns>
        private static string BuildNavigateActionsListText(string srcFileFullPath, string docTitle)
        {
            try
            {
                //原来的思路完全错了，现在应该按工作区管理器而不是物理磁盘目录来生成导航列表！
                var curItem = Globals.MainWindow.FindWorkspaceTreeViewItem(srcFileFullPath);
                if (curItem == null) return "";

                var sb = new StringBuilder();
                sb.Append("\n");
                sb.Append($"◆ {docTitle}</p>\n");   // <p  ..> 开始标签要最后加

                var parentItem = curItem.ParentWorkspaceTreeViewItem;
                while (parentItem != null)
                {
                    sb.Insert(0, "\n");
                    string shortName = parentItem.Title;

                    // parentItem 的类型必定是普通目录，所以必定有元文件，因此只要传入元文件路径即可。
                    var linkText = Globals.MainWindow.BuildLinkText(parentItem.MetaFilePath, srcFileFullPath, ref shortName, true);
                    sb.Insert(0, $"<a class=\"nav-action\" href=\"{linkText}\">{shortName}</a></p>");
                    // <p ...> 开头标签要后加。

                    parentItem = parentItem.ParentWorkspaceTreeViewItem;
                }

                var lines = sb.ToString().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
                var preFix = "";
                var sb2 = new StringBuilder();

                var count = 0;
                if (lines.Length > 0)
                {
                    sb2.Append("\n<p class=\"nav-paragraph\">■ " + lines[0] + "\n");
                    for (int i = 1; i < lines.Length; i++)
                    {
                        if (i == lines.Length - 1)
                        {
                            lines[i] = "<p class=\"nav-paragraph\">" + preFix + "└" + lines[i];
                        }
                        else
                        {
                            lines[i] = "<p class=\"nav-paragraph\">" + preFix + "└△ " + lines[i];
                        }
                        sb2.Append(lines[i] + "\n");
                        preFix += '　';
                        count++;
                    }

                    if (count > 0)
                    {
                        return "\n<div class=\"nav-links-list\" style=\"text-indent:0px;\">\n" + sb2.ToString() + "\n</div>\n";
                    }
                    else
                    {
                        return "";
                    }
                }

                return srcFileFullPath;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 处理源文本行中的环境变量文本片段。在 LME 中，“环境变量”是通过模仿 C 系列语言的“/* */”注释的方式
        /// 来实现（但是不支持多行）。
        /// </summary>
        /// <param name="srcText">源文本</param>
        /// <returns></returns>
        public static string FormatInlineEnviromentValueSpans(string srcText, Dictionary<string, string> dict, bool forEpub = false)
        {
            string r = srcText;

            var regCodeLine = new Regex(@"(^[ 　]{4,}.*)|([\t].*)");
            if (regCodeLine.Match(r).Success) return r;   //代码行中忽略。

            var reg = new Regex(@"(^|(?<=[^`]))/\*.*?\*/($|(?=[^`]))");
            var matches = reg.Matches(srcText);
            if (matches.Count <= 0) return srcText;
            for (int i = matches.Count - 1; i >= 0; i--)
            {
                var m = matches[i];
                var replaceValue = GetReplacedValue(m.Value, dict, forEpub);
                r = r.Substring(0, m.Index) + replaceValue + r.Substring(m.Index + m.Length);
            }

            return r;
        }

        /// <summary>
        /// 获取环境变量的替代值。
        /// </summary>
        /// <returns></returns>
        private static string GetReplacedValue(string key, Dictionary<string, string> dict, bool forEpub = false)
        {
            if (string.IsNullOrWhiteSpace(key)) return "";
            if (dict == null || dict.Count <= 0) return key;

            if (key.StartsWith("/*") == false || key.EndsWith("*/") == false) return key;

            var key2 = key.Substring(2, key.Length - 4);
            var indexOfminus = key2.IndexOf("-");
            if (indexOfminus > 0) key2 = key2.Substring(0, indexOfminus);  //至少留一个字符。
            if (dict.ContainsKey(key2))
            {
                var value = dict[key2];
                if (string.IsNullOrEmpty(value))
                {
                    if (key2.ToLower() == "nav")
                    {
                        return "";  // /*Nav*/ 这个环境变量在为空值时直接忽略——它应有这个智能性。
                    }
                    else
                        return "/\\*" + key2 + "-<span style=\"color:red;font-weight:bold;\">空值！</span>\\*/";
                }
                if (value == "Hide") return "";
                return value;
            }

            if (forEpub && (key2.ToLower() == "nav" || key2.ToLower() == "sublinkslist"))
            {
                return "";
            }

            return "/\\*" + key2 + "-<span style=\"color:red;font-weight:bold;\">未定义！</span>\\*/";  //原文本返回。
        }

        /// <summary>
        /// 是否任务列表项文本。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsTaskLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            Regex regex = new Regex(@"^ {0,3}\[[ 　\t]*[\-\+\%\#－＋％＃][ 　\t]*\][^\(].*$");
            var match = regex.Match(lineText);
            return match.Success;
        }

        /// <summary>
        /// 是否“废弃”任务列表项。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsAbortedTaskLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            Regex regex = new Regex(@"^ {0,3}\[[ 　\t]*[\#＃][ 　\t]*\][^\(].*$");
            var match = regex.Match(lineText);
            return match.Success;
        }

        /// <summary>
        /// 是否以分号开头的注释文本。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsCommentLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            if (lineText.StartsWith("    ") || lineText.StartsWith("\t")) return false;//这是代码块

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            if (tmp.StartsWith(";") || tmp.StartsWith("；") || tmp.StartsWith(":") || tmp.StartsWith("：")) return true;

            return false;
        }

        /// <summary>
        /// 是否指示“要编译 Html 菜单”的标记文本。
        /// </summary>
        /// <param name="lineText">文本行。</param>
        public static bool IsMenuMarkText(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            if (lineText.StartsWith("    ") || lineText.StartsWith("\t")) return false;//这是代码块

            Regex r = new Regex(@"^[   ]{0,3}[;；](\[?(([mMｍＭ][eEｅＥ][nNｎＮ][uUｕＵ])|(菜单))\]?)[:：].*$");
            var match = r.Match(lineText);
            if (match != null && match.Success)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 是否时间标签文本。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsDateLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            if (lineText.StartsWith("    ") || lineText.StartsWith("\t")) return false;//这是代码块

            //Regex r = new Regex(@"^[ 　]{0,3}\[((((1[6-9]|[2-9]\d)\d{2})[-.,，。－、．/年](0?[13578]|1[02])[-.,，。－、．/月](0?[1-9]|[12]\d|3[01])日{0,1})|(((1[6-9]|[2-9]\d)\d{2})[-.,，。－、．/年](0?[13456789]|1[012])[-.,，。－、．/月](0?[1-9]|[12]\d|30)日{0,1})|(((1[6-9]|[2-9]\d)\d{2})[-.,，。－、．/年]0?2[-.,，。－、．/月](0?[1-9]|1\d|2[0-8])日{0,1})|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))[-.,，。－、．/年]0?2-29[-.,，。－、．/月])日{0,1})\]");
            Regex r = new Regex(@"^[ 　]{0,3}\[((\d{4,4}[-.,，。－、．/年][01]{0,1}\d[-.,，。－、．/月][0123]{0,1}\d日{0,1}([ 　]{1,}\d{1,2}[:：]\d{1,2}){0,1}\](\[[ 　\t]*[\-\%\+\#－＋％＃][ 　\t]*)|[@])\]){0,1}");
            var match = r.Match(lineText);
            if (match != null && match.Success)
            {
                var txt = match.Value.TrimStart(new char[] { ' ', '　', '\t', });
                if (txt.StartsWith("[@]"))
                {
                    return true;
                }
                var result = GetDateFromDateLine(lineText);
                if (result == null) return false;
                if (result.HasValue == false) return false;

                return true;
            }
            return false;
        }

        /// <summary>
        /// 格式化日期标签文本行的日期标志文本部分。
        /// </summary>
        /// <param name="dateMarkText">日期标志文本。形如：[2016-2-1]之类。</param>
        public static string FormatDateLineMark(string dateMarkText)
        {
            var trimChars = new char[] { ' ', '　', '\t' };
            return dateMarkText.Replace("/", "-").Replace(".", "-").Replace(",", "-").Replace("，", "-")
                .Replace("。", "-").Replace("－", "-").Replace("、", "-").Replace("．", "-")
                .Replace("年", "-").Replace("月", "-").Replace("日", "").Replace("－", "-").Replace("：", ":").Trim(trimChars);
        }

        /// <summary>
        /// 格式化日期文本行。
        /// </summary>
        /// <param name="dateLineText">要格式化的日期文本行。</param>
        public static string FormatDateLine(string dateLineText)
        {
            if (string.IsNullOrWhiteSpace(dateLineText)) return "";
            var replacedLine = dateLineText.Replace("－", "-");
            var index = replacedLine.IndexOf("]");
            if (index < 0) return dateLineText;//原样返回

            string header;
            string tail;
            if (replacedLine.Length >= index + 2)
            {
                char c = replacedLine[index + 1];
                if (c == '-' || c == '－' || c == '%' ||
                    c == '#' || c == '+')
                {
                    header = dateLineText.Substring(0, index + 1) + c;
                    tail = dateLineText.Substring(index + 2);
                }
                else
                {
                    header = dateLineText.Substring(0, index + 1);
                    tail = dateLineText.Substring(index + 1);
                }
            }
            else
            {
                header = dateLineText.Substring(0, index + 1);
                tail = dateLineText.Substring(index + 1);
            }

            var trimChars = new char[] { ' ', '　', '\t' };
            //-.,，。－、．/

            var secHeaderMark = "";
            var tailTrim = tail.Trim(trimChars);
            if (tail.StartsWith("[-]") || tail.StartsWith("[－]") || tail.StartsWith("[%]") ||
                tail.StartsWith("[#]") || tail.StartsWith("[+]"))
            {
                secHeaderMark = tail.Substring(0, 3).Replace("[－]", "[-]");
                tail = tailTrim.Substring(3);
            }

            return " " + FormatDateLineMark(header) + secHeaderMark + " " + tail.Trim(trimChars);

        }

        /// <summary>
        /// 从日期文本行中取日期标志文本部分。
        /// </summary>
        /// <param name="dateLine">日期文本行。</param>
        public static DateTime? GetDateFromDateLine(string dateLine)
        {
            if (string.IsNullOrWhiteSpace(dateLine)) return null;

            var txt = dateLine.TrimStart(new char[] { ' ', '　', '\t', });
            if (txt.StartsWith("[@]"))
            {
                return new DateTime(1970, 1, 1, 0, 0, 0);   // [@] 开头的任务直接取最早日期
            }

            var index = dateLine.IndexOf("]");

            DateTime dt;
            if (index < 0)
            {
                if (DateTime.TryParse(dateLine, out dt)) return dt;

                return null;//原样返回
            }

            var header = dateLine.Substring(0, index + 1).Replace("[", "").Replace("]", "");

            if (DateTime.TryParse(header, out dt)) return dt;

            return null;
        }

        /// <summary>
        /// 取时间文本中的任务完成状态标记文本。
        /// </summary>
        /// <param name="dateLine">时间文本。</param>
        public static string GetDateLineStatusMark(string dateLine)
        {
            if (string.IsNullOrWhiteSpace(dateLine)) return "";

            Regex regex = new Regex(@"\]\[[ 　\t]*[-－+#%][ 　\t]*\]");
            var match = regex.Match(dateLine);
            if (match != null && match.Success)
            {
                return dateLine.Substring(match.Index + 1, match.Length - 1).Replace(" ", "").Replace("　", "").Replace("\t", "");
            }

            return "";
        }

        /// <summary>
        /// 取时间文本行中的内容文本（表示该时间标签指代的时间发生的事件的具体意义，而非时间标签所指代的时间）。
        /// </summary>
        /// <param name="dateLine">时间文本。</param>
        public static string GetContentTextOfDateLine(string dateLine)
        {
            if (string.IsNullOrWhiteSpace(dateLine)) return "";

            // Regex regex = new Regex(@"\[[ 　\t]{0,}[-－%#+][ 　\t]{0,}\][ 　\t].*$");
            Regex regex = new Regex(@"(?<=(^[ 　]{0,3}\[((\d{4,4}[-.,，。－、．/年][01]{0,1}\d[-.,，。－、．/月][0123]{0,1}\d日{0,1}([ 　]{1,}\d{1,2}[:：]\d{1,2}){0,1})|[@])\](\[[ 　\t]*[\-\%\+\#－＋％＃][ 　\t]*\]){0,1})).*$");
            var match = regex.Match(dateLine);
            if (match != null && match.Success)
            {
                var index = match.Value.IndexOf("]");
                if (index < 0) return match.Value.Trim(new char[] { ' ', '　', '\t' });
                return match.Value.Substring(index + 1).Trim(new char[] { ' ', '　', '\t' });
            }
            return "";
        }

        /// <summary>
        /// 是否以冒号开头的注释。这种注释会被编译进 Html 文档，并在 Html 页面上呈现特殊效果。
        /// </summary>
        /// <param name="lineText">源文本。</param>
        public static bool IsCompileCommentLine(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;
            if (lineText.StartsWith("    ") || lineText.StartsWith("\t")) return false;//这是代码块

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            if (tmp.StartsWith(":") || tmp.StartsWith("：")) return true;

            return false;
        }

        /// <summary>
        /// 取文档标题文本。
        /// </summary>
        /// <param name="lineText">以 % 开头的文本行。</param>
        /// <returns>返回去除 % 标记的其它文本。</returns>
        public static string GetDocumentTitle(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return null;

            var text = lineText.Replace("　", " ");
            if (text.StartsWith("    ") || text.StartsWith("\t")) return null;
            //注意：[空格][Tab]不会被编译为Code行。

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            if (lineText.StartsWith("%")) return tmp.Substring(1);

            var tmp2 = tmp.Replace("Ｔ", "t").Replace("ｔ", "t").Replace("Ｉ", "i").Replace("ｉ", "i").Replace("Ｌ", "l").Replace("ｌ", "l").Replace("Ｅ", "e").Replace("ｅ", "e")
                .Replace("〉", ">").ToLower();
            if (tmp2.StartsWith("title>")) return tmp.Substring(6);

            if (tmp.StartsWith("标题>") || tmp.StartsWith("标题〉")) return tmp.Substring(3);

            return null;
        }

        /// <summary>
        /// 取文档副标题。
        /// 如果文本行以 %－－ 或 %-- 或 %—— 开头，则为副标题。
        /// </summary>
        /// <param name="lineText">文本行。</param>
        /// <returns>注意：返回值不包括破折号！</returns>
        public static string GetSubTitle(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return null;

            var text = lineText;
            if (text.StartsWith("    ") || text.StartsWith("\t")) return null;
            //注意：[空格][Tab]不会被编译为Code行。

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            var regex = new Regex(@"^%[－\-—]{2,}");
            var match = regex.Match(tmp);
            if (match.Success)
            {
                return tmp.Substring(match.Length);
            }

            var tmp2 = tmp.Replace("Ｓ", "s").Replace("Ｕ", "u").Replace("Ｂ", "b").Replace("Ｔ", "t").Replace("ｔ", "t").Replace("Ｉ", "i").Replace("ｉ", "i").Replace("Ｌ", "l").Replace("ｌ", "l").Replace("Ｅ", "e").Replace("ｅ", "e")
                .Replace("〉", ">").ToLower();
            if (tmp2.StartsWith("subtitle>")) return tmp.Substring(6);

            if (tmp.StartsWith("副标题>") || tmp.StartsWith("副标题〉")) return tmp.Substring(3).Trim(new char[] { '－', '-', '—', });

            return null;
        }

        /// <summary>
        /// 判断文本行是否页脚文本。（页脚文本以 @ 或 · 开头。）
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <returns></returns>
        public static bool IsPageFoot(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            var text = lineText.Replace("　", " ");
            if (text.StartsWith("    ") || text.StartsWith("\t")) return false;
            //注意：[空格][Tab]不会被编译为Code行。

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            if (tmp.StartsWith("@") || tmp.StartsWith("·")) return true;

            return false;
        }

        /// <summary>
        /// 判断文本行是否页眉文本。（页眉文本以波形符~开头。）
        /// </summary>
        /// <param name="lineText">源文本。</param>
        /// <returns>返回去除波形符以后的其余文本。</returns>
        public static bool IsPageHeader(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return false;

            var text = lineText.Replace("　", " ");
            if (text.StartsWith("    ") || text.StartsWith("\t")) return false;
            //注意：[空格][Tab]不会被编译为Code行。

            var tmp = lineText.TrimStart(new char[] { ' ', '\t', '　' });
            if (tmp.StartsWith("~")) return true;

            return false;
        }

        /// <summary>
        /// 判断路径指向的 Markdown 文档是否被加密。
        /// </summary>
        /// <param name="fullFilePath">Markdown 文件路径。</param>
        public static bool IsEncripied(string fullFilePath)
        {
            if (String.IsNullOrWhiteSpace(fullFilePath) || File.Exists(fullFilePath) == false) return false;

            System.IO.StreamReader s = File.OpenText(fullFilePath);
            var fstEncriptLine = s.ReadLine();
            if (string.IsNullOrEmpty(fstEncriptLine)) return false;

            var fstDecriptedLine = SetPasswordPanel.TextDecrypt(fstEncriptLine.Replace("[[<r>]]", "\r").Replace("[[<n>]]", "\n").ToCharArray(), "DyBj#PpBb");
            if (fstDecriptedLine.Contains("Password:") && fstDecriptedLine.Contains("Question:") && fstDecriptedLine.Contains("|"))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 用于决定按何种方式将 Markdown 切分为幻灯片。
        /// </summary>
        public enum PresentateHtmlSplitterType
        {
            /// <summary>
            /// 只演示自定义折叠区中的内容，其它忽略。
            /// </summary>
            Region,
            /// <summary>
            /// 演示所有一级标题下属的内容，但不包括第一个一级标题之前的内容。
            /// 这样设计的原因在于：第一个一级标题前可能只有文档标题而没有其它内容——这时候会显示为一个空页面。
            /// </summary>
            TopLevelHeader,
            /// <summary>
            /// 演示时按水平线分割。
            /// </summary>
            HorizontalLine,
            /// <summary>
            /// 按照文档中定义的方式来，方式应为下列三种之一。
            /// 如果文档中没有定义，则默认按一级标题（TopLevelHeader）。
            /// </summary>
            ByDocument,
            /// <summary>
            /// 直接演示整个文档。与“调用外部浏览器预览”相比，唯一的不同只是使用不同的CSS，从而使得字体不同。
            /// </summary>
            WholeDocument,
            /// <summary>
            /// 演示指定的文本片段。
            /// </summary>
            TextSegment,
        }

        /// <summary>
        /// 将一个 Markdown 文件按水平线拆分成多个文件，再编译。
        /// 这是为了演示。
        /// </summary>
        /// <param name="srcFilePath"></param>
        /// <param name="htmlHeadersCollpase"></param>
        /// <param name="htmlTitle"></param>
        /// <returns></returns>
        public static List<CustomUri> Compile(string srcFilePath,
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
            PresentateHtmlSplitterType presentateHtmlSplitterType,
            ref List<string> htmlFilesList, ref List<string> mdFilesList,
            bool onlyPresentateHeaders = false,
            string textSetment = null,
            string previewPageLink = "",
            string nextPageLink = "")
        {
            List<CustomUri> uris = new List<CustomUri>();
            htmlFilesList.Clear();
            mdFilesList.Clear();

            if (File.Exists(srcFilePath) == false) return uris;

            var sourceText = File.ReadAllText(srcFilePath) + "\r\n";  // 防止某些元素以最后一个有效行结尾造成无法编译。

            switch (presentateHtmlSplitterType)
            {
                case PresentateHtmlSplitterType.Region:
                    {
                        SplitAndCompileByRegions(srcFilePath, sourceText, htmlHeadersCollpase,
                            ref uris, ref htmlFilesList, ref mdFilesList);
                        break;
                    }
                case PresentateHtmlSplitterType.TopLevelHeader:
                    {
                        SplitAndCompileByTopLevelHeaders(srcFilePath, sourceText, htmlHeadersCollpase,
                            ref uris, ref htmlFilesList, ref mdFilesList, onlyPresentateHeaders);
                        break;
                    }
                case PresentateHtmlSplitterType.WholeDocument:
                    {
                        CompileByWholeDocument(srcFilePath, sourceText, htmlHeadersCollpase,
                            ref uris, ref htmlFilesList, ref mdFilesList);
                        break;
                    }
                case PresentateHtmlSplitterType.TextSegment:
                    {
                        CompileTextSegment(srcFilePath, sourceText, htmlHeadersCollpase,
                            ref uris, ref htmlFilesList, ref mdFilesList, textSetment);
                        break;
                    }
                case PresentateHtmlSplitterType.HorizontalLine:
                default:
                    {
                        SplitAndCompileByHorizontalLine(srcFilePath, sourceText, htmlHeadersCollpase,
                            ref uris, ref htmlFilesList, ref mdFilesList);
                        break;
                    }
            }
            return uris;
        }

        public static void CompileTextSegment(string srcFilePath, string sourceText,
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
            ref List<CustomUri> uris,
            ref List<string> htmlFilesList,
            ref List<string> mdFilesList,
            string textSegment)
        {
            string htmlTitle = "";

            var lines = File.ReadLines(srcFilePath);
            foreach (var line in lines)
            {
                var title = GetDocumentTitle(line);
                if (title != null)
                {
                    htmlTitle = title;
                    break;
                }
            }

            var newPathPrefix = "";
            if (srcFilePath.ToLower().EndsWith(".md"))
            {
                newPathPrefix = srcFilePath.Substring(0, srcFilePath.Length - 3);
            }
            else newPathPrefix = srcFilePath;
            var pageFileName = newPathPrefix + "_tmp~.md";
            File.WriteAllText(pageFileName, $"\r\n%{htmlTitle}\r\n\r\n{textSegment}");
            var newHtmlFilePath = CompileOrGetHtmlFile(pageFileName, htmlHeadersCollpase,
                 PresentateHtmlSplitterType.TextSegment, 1, ref htmlTitle, false, true, false,
                 Globals.MainWindow.mainTabControl.FontSize / 16);

            if (string.IsNullOrWhiteSpace(newHtmlFilePath) == false)
            {
                mdFilesList.Add(pageFileName);
                htmlFilesList.Add(newHtmlFilePath);

                uris.Add(new CustomUri() { Uri = new Uri("file:///" + newHtmlFilePath), MarkdownTitle = htmlTitle, });
            }
        }

        public static void CompileByWholeDocument(string srcFilePath, string sourceText,
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
            ref List<CustomUri> uris,
            ref List<string> htmlFilesList,
            ref List<string> mdFilesList)
        {
            string htmlTitle = "";

            var lines = File.ReadLines(srcFilePath);
            foreach (var line in lines)
            {
                var title = GetDocumentTitle(line);
                if (title != null)
                {
                    htmlTitle = title;
                    break;
                }
            }

            var newPathPrefix = "";
            if (srcFilePath.ToLower().EndsWith(".md"))
            {
                newPathPrefix = srcFilePath.Substring(0, srcFilePath.Length - 3);
            }
            else newPathPrefix = srcFilePath;
            var pageFileName = newPathPrefix + "_tmp~.md";
            File.WriteAllText(pageFileName, sourceText);
            var newHtmlFilePath = CompileOrGetHtmlFile(pageFileName, htmlHeadersCollpase,
                 PresentateHtmlSplitterType.WholeDocument, 1, ref htmlTitle, false, true, false, 1.5f);

            if (string.IsNullOrWhiteSpace(newHtmlFilePath) == false)
            {
                mdFilesList.Add(pageFileName);
                htmlFilesList.Add(newHtmlFilePath);

                uris.Add(new CustomUri() { Uri = new Uri("file:///" + newHtmlFilePath), MarkdownTitle = htmlTitle, });
            }
        }

        public static void SplitAndCompileByHorizontalLine(string srcFilePath, string sourceText,
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
            ref List<CustomUri> uris,
            ref List<string> htmlFilesList,
            ref List<string> mdFilesList)
        {
            var newPathPrefix = "";
            if (srcFilePath.ToLower().EndsWith(".md"))
            {
                newPathPrefix = srcFilePath.Substring(0, srcFilePath.Length - 3);
            }
            else newPathPrefix = srcFilePath;

            string htmlTitle = "";

            var lines = File.ReadLines(srcFilePath);

            List<string> pageTexts = new List<string>();

            StringBuilder sb = new StringBuilder();
            foreach (var line in lines)
            {
                var title = GetDocumentTitle(line);
                if (title != null && htmlTitle == "")
                {
                    htmlTitle = title;
                    continue;
                }

                if (IsHorizontalLineText(line))
                {
                    pageTexts.Add(sb.ToString());
                    sb = new StringBuilder();
                    sb.Append("\r\n");
                    continue;
                }

                sb.Append(line);
                sb.Append("\r\n");
            }

            pageTexts.Add(sb.ToString());

            for (int i = 0; i < pageTexts.Count; i++)
            {
                var pageText = "%" + htmlTitle + "\r\n\r\n" + pageTexts[i];
                var pageFileName = newPathPrefix + $"_{i + 1}_tmp~.md";
                File.WriteAllText(pageFileName, pageText);
                var newHtmlFilePath = CompileOrGetHtmlFile(pageFileName, htmlHeadersCollpase,
                    PresentateHtmlSplitterType.HorizontalLine,
                    i + 1, ref htmlTitle, false, true, false, 1.5f);

                if (string.IsNullOrWhiteSpace(newHtmlFilePath) == false)
                {
                    mdFilesList.Add(pageFileName);
                    htmlFilesList.Add(newHtmlFilePath);

                    uris.Add(new CustomUri() { Uri = new Uri("file:///" + newHtmlFilePath), MarkdownTitle = htmlTitle, });
                }
            }
        }

        public static void SplitAndCompileByTopLevelHeaders(string srcFilePath, string sourceText,
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
            ref List<CustomUri> uris,
            ref List<string> htmlFilesList,
            ref List<string> mdFilesList,
            bool onlyPresentateHeaders)
        {
            var newPathPrefix = "";
            if (srcFilePath.ToLower().EndsWith(".md"))
            {
                newPathPrefix = srcFilePath.Substring(0, srcFilePath.Length - 3);
            }
            else newPathPrefix = srcFilePath;

            string htmlTitle = "";

            var lines = File.ReadLines(srcFilePath);

            List<string> pageTexts = new List<string>();

            StringBuilder sb = new StringBuilder();
            foreach (var line in lines)
            {
                var title = GetDocumentTitle(line);
                if (title != null && htmlTitle == "")
                {
                    htmlTitle = title;
                    continue;
                }

                Regex regexTopLevelHeader = new Regex(@"^[ 　]{0,3}[#＃][^#＃]*$");
                var match = regexTopLevelHeader.Match(line);

                if (match != null && match.Success)
                {
                    pageTexts.Add(sb.ToString());
                    sb = new StringBuilder();
                    sb.Append(line);
                    sb.Append("\r\n");
                    continue;
                }

                if (onlyPresentateHeaders)
                {
                    Regex regexHeader = new Regex(@"^[ 　]{0,3}[#＃].*$");
                    var matchHeader = regexHeader.Match(line);
                    if (matchHeader != null && matchHeader.Success)
                    {
                        sb.Append(line);
                        sb.Append("\r\n");
                    }
                }
                else
                {
                    sb.Append(line);
                    sb.Append("\r\n");
                }
            }

            pageTexts.Add(sb.ToString());

            for (int i = 1; i < pageTexts.Count; i++)//从1开始，不显示第一个一级标题前面的内容。
            {
                var pageText = "%" + htmlTitle + "\r\n\r\n" + pageTexts[i];
                var pageFileName = newPathPrefix + $"_{i + 1}_tmp~.md";
                File.WriteAllText(pageFileName, pageText);
                var newHtmlFilePath = CompileOrGetHtmlFile(pageFileName, htmlHeadersCollpase,
                     PresentateHtmlSplitterType.TopLevelHeader, i, ref htmlTitle, false, true, false, 1.5f);

                if (string.IsNullOrWhiteSpace(newHtmlFilePath) == false)
                {
                    mdFilesList.Add(pageFileName);
                    htmlFilesList.Add(newHtmlFilePath);

                    uris.Add(new CustomUri() { Uri = new Uri("file:///" + newHtmlFilePath), MarkdownTitle = htmlTitle, });
                }
            }
        }

        public static void SplitAndCompileByRegions(string srcFilePath, string sourceText,
            MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
            ref List<CustomUri> uris,
            ref List<string> htmlFilesList,
            ref List<string> mdFilesList)
        {
            //★注意：C# 正则表达式在多行查询时，要匹配包含换行符在内的所有字符，必须使用([\s\S]*)表示。
            //        加上?是表示非贪婪模式。
            Regex regionRegex = new Regex(@"(?<=(^[ 　]{0,3}([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ][ 　\t]*)?([ｉｗｅｑiweq][ 　\t]*)?\{.*$))[\s\S]*?(?=(^[ 　]{0,3}\}[ 　\t]*([rｒRＲ][eｅEＥ][gｇGＧ][iｉIＩ][oｏOＯ][nｎNＮ])?.*$))", RegexOptions.Multiline);
            var matches = regionRegex.Matches(sourceText);

            if (matches == null || matches.Count <= 0)
            {
                return;
            }

            var newPathPrefix = "";
            if (srcFilePath.ToLower().EndsWith(".md"))
            {
                newPathPrefix = srcFilePath.Substring(0, srcFilePath.Length - 3);
            }
            else newPathPrefix = srcFilePath;

            string htmlTitle = "";

            var lines = File.ReadLines(srcFilePath);
            foreach (var line in lines)
            {
                var title = GetDocumentTitle(line);
                if (title != null)
                {
                    htmlTitle = title;
                    break;
                }
            }

            for (int i = 0; i < matches.Count; i++)
            {
                var pageText = "%" + htmlTitle + "\r\n\r\n" + matches[i].Value;
                var pageFileName = newPathPrefix + $"_{i + 1}_tmp~.md";
                File.WriteAllText(pageFileName, pageText);
                var newHtmlFilePath = CompileOrGetHtmlFile(pageFileName, htmlHeadersCollpase,
                     PresentateHtmlSplitterType.Region, i + 1, ref htmlTitle, false, true, false, 1.5f);

                if (string.IsNullOrWhiteSpace(newHtmlFilePath) == false)
                {
                    mdFilesList.Add(pageFileName);
                    htmlFilesList.Add(newHtmlFilePath);

                    uris.Add(new CustomUri() { Uri = new Uri("file:///" + newHtmlFilePath), MarkdownTitle = htmlTitle, });
                }
            }
        }

        internal static readonly string switchThemeFunc = @"
function SwitchTheme()
{
    var css = document.getElementById('theme_link');
    var custom_css = document.getElementById('custom_theme_link');
    var menu_css = document.getElementById('menu_theme_link');
    var custom_menu_css = document.getElementById('custom_menu_theme_link');
    var url;
    var custom_url;
    var menu_url;
    var custom_menu_url;
    if (theme_css_prefix != 'presentation_'){ theme_css_prefix = 'lesson_' }
    var theme = 'light';
	if (/.*_dark.css$/.test(css.getAttribute('href'))) theme = 'dark';
    if (theme == 'light')
    {
        url = path_prefix + theme_css_prefix + 'dark.css';
        custom_url = path_prefix + 'custom_' + theme_css_prefix + 'dark.css';
        menu_url = path_prefix + 'menu_dark.css';
        custom_menu_url = path_prefix + 'custom_menu_dark.css';
        window.name = 'dark';
    }
    else
    {
        url = path_prefix + theme_css_prefix + 'light.css';
        custom_url = path_prefix + 'custom_' + theme_css_prefix + 'light.css';
        menu_url = path_prefix + 'menu_light.css';
        custom_menu_url = path_prefix + 'custom_menu_light.css';
        window.name = 'light';
    }
    if (css) css.setAttribute('href', url);
    if (custom_css) custom_css.setAttribute('href', custom_url);
    if (menu_css) menu_css.setAttribute('href', menu_url);
    if (custom_menu_css) custom_menu_css.setAttribute('href', custom_menu_url);
}
";
        internal static readonly string loadThemeFunc = @"
function LoadTheme(new_theme)
{
    var css = document.getElementById('theme_link');
    var custom_css = document.getElementById('custom_theme_link');
    var menu_css = document.getElementById('menu_theme_link');
    var custom_menu_css = document.getElementById('custom_menu_theme_link');
    var url;
    var custom_url;
    var menu_url;
    var custom_menu_url;
    if(theme_css_prefix != 'presentation_'){ theme_css_prefix = 'lesson_' }
    var theme = 'light';
	if (/.*_dark.css$/.test(css.getAttribute('href'))) theme = 'dark';
    if (new_theme && new_theme != theme)
    {
        if (new_theme == 'dark')
        {
            url = path_prefix + theme_css_prefix + 'dark.css';
            custom_url = path_prefix + 'custom_' + theme_css_prefix + 'dark.css';
            menu_url = path_prefix + 'menu_dark.css';
            custom_menu_url = path_prefix + 'custom_menu_dark.css';
        }
        else
        {
            url = path_prefix + theme_css_prefix + 'light.css';
            custom_url = path_prefix + 'custom_' + theme_css_prefix + 'light.css';
            menu_url = path_prefix + 'menu_light.css';
            custom_menu_url = path_prefix + 'custom_menu_light.css';
        }
        if (css) css.setAttribute('href', url);
        if (custom_css) custom_css.setAttribute('href', custom_url);
        if (menu_css) menu_css.setAttribute('href', menu_url);
        if (custom_menu_css) custom_menu_css.setAttribute('href', custom_menu_url);
    }
}
";
        internal static readonly string loadUserSelectedThemeFunc = @"
function LoadUserSelectedTheme()
{
    if(window.name == 'dark' || window.name == 'light')
    {
    LoadTheme(window.name);
    }
}
";
        internal static readonly string getThemeNameFunc = @"
function GetThemeName()
{
    var css = document.getElementById('theme_link');
    var theme = 'light';
    if (/.*_dark.css$/.test(css.getAttribute('href'))) theme = 'dark';
    return theme;
}
";
        internal static readonly string showImagesFunc = @"
function ShowImages()
{
    imgs = document.getElementsByTagName('img');
    imgsnum = imgs.length;
    for (imgi = 0; imgi < imgsnum; imgi++)
    {
        if ((typeof(imgs[imgi].src) == 'undefined' || imgs[imgi].src == '') &&
             imgs[imgi].getAttribute('thissrc') != null)
            imgs[imgi].src = imgs[imgi].getAttribute('thissrc');
    }
}
";
        internal static readonly string hideIamgesFunc = @"
function HideImages()
{
    imgs = document.getElementsByTagName('img');
    imgsnum = imgs.length;
    for (imgi = 0; imgi < imgsnum; imgi++)
    {
        if (typeof(imgs[imgi].src) != 'undefined' && imgs[imgi].src != '')
            imgs[imgi].thissrc = imgs[imgi].getAttribute('src');
            imgs[imgi].src = '';
    }
}
";
        /// <summary>
        /// 编译指定路径的 Markdown 文件。
        /// </summary>
        /// <param name="srcFilePath"></param>
        /// <returns></returns>
        public static string Compile(string srcFilePath, bool ignoreNavAndSubLinks = true)
        {
            string htmlTitle = "";
            return CustomMarkdownSupport.CompileOrGetHtmlFile(srcFilePath,
                Globals.MainWindow.HtmlHeadersCollapse, null, 1, ref htmlTitle, false, true, false, 1, "", "", "", "", ignoreNavAndSubLinks);
        }

        /// <summary>
        /// 将 Markdown 文件编译为 Html 网页文件。基本流程如下：
        ///     1.对 Markdown 文件中的文本进行格式化；
        ///     2.移除不必要编译进 Html 中的行；
        ///     3.对 Markdown 进行预处理，将一些自定义的 Markdown 元素先编译为 Html 标签；
        ///     4.调用 MarkdownSharp 对经过预处理的 Markdown 文档进行编译；
        ///     5.对 MarkdownSharp 编译后的 Html 文本进行追加处理，使之支持六级标题等功能；
        ///     6.根据偏好设置决定是否对追加处理后的 Html 文本进行格式化。
        /// </summary>
        /// <param name="srcFilePath">要编译的 Markdown 源文件的路径。</param>
        /// <param name="htmlHeadersCollpase">在编译好的 Html 文档中六级标题是否支持折叠功能。</param>
        /// <param name="presentateHtmlSplitterType">编译整个文档时应传入 null。</param>
        /// <param name="startH1Number">此参数用以指定&lt;H1&gt;的编号从什么值开始，通常应传入1。
        /// 此参数是为了编译切块的幻灯片 Html 文档而存在的。</param>
        /// <param name="htmlTitle">编译好的 Html 文档应以什么文本作为标题。此标题应由 Markdown 文件内部设置的“%xxx”行决定。</param>
        /// <param name="withoutAdonors">不向页面添加标题、脚注、编译时间等元素。</param>
        /// <param name="getIfExist">此参数为真时，如果存在修改时间比对应 Markdown 文件晚的 Html 文件，不编译，直接返回。只有“预览”时应调用此方法。</param>
        /// <returns>如果编译成功，返回新文件路径。</returns>
        public static string CompileOrGetHtmlFile(string srcFilePath, MainWindow.HtmlHeadersCollapseType htmlHeadersCollpase,
              PresentateHtmlSplitterType? presentateHtmlSplitterType, int startH1Number,
              ref string htmlTitle, bool addBackToIndexLink, bool withoutAdonors = false, bool getIfExist = true, double webBrowserZoom = 1,
              string previewPageLink = "", string nextPageLink = "", string previewPagePath = "", string nextPagePath = "",
              bool ignoreNavAndSubLinks = false, bool forEpub = false, string originalMarkdownFilePath = "", List<MarkdownFile> mdFilesList = null)
        {
            if (srcFilePath.FileExist() != true) return null;

            codeSampleIndex = 1; // 每次开始编译前都应重置此值为1。

            if (originalMarkdownFilePath == "")
            {
                // 有时候，编译的文档并不在其原始路径（例如导出 epub 电子书时，须将 Markdown 文档移动到工作区根目录下进行编译。
                // 此时，如果直接使用当前实际路径，则其中的图像链接无法计算准确，须传入原始路径。
                // Epub 中的图像文件只能集中管理（类似 LME 的 QuickImage 功能），所以要将链接的图像复制到一起，然后对应地重新更改图像链接的相对引用路径。
                originalMarkdownFilePath = srcFilePath;
            }

            var newFileName = srcFilePath.Substring(0, srcFilePath.Length - 3) + ".html";

            bool alwaysCompileBeforePreview = Globals.MainWindow.AlwaysCompileBeforePreview;
            if (alwaysCompileBeforePreview != true)
            {
                if (getIfExist)
                {
                    if (newFileName.IsNewerThanAnotherFile(srcFilePath))
                        return newFileName;
                }
            }

            // 相对于工作区根目录的路径
            string directoryLevelMark = GetDirectoryLevelMark(srcFilePath);

            string shortFileName;
            int lastIndex = srcFilePath.LastIndexOf("\\");
            if (lastIndex >= 0)
            {
                string name = srcFilePath.Substring(lastIndex + 1);
                if (string.IsNullOrEmpty(name))
                {
                    shortFileName = "unnamed.md";
                }
                else shortFileName = name;
            }
            else
            {
                shortFileName = "unnamed.md";
            }

            string htmlDocumentTitle;
            string htmlDocumentPageHeader;
            string footerText;

            MarkdownSharp.Markdown md = new MarkdownSharp.Markdown();

            string contentText;

            bool isEncrypted = false;
            string password, passwordTip;
            isEncrypted = IsFileEncrypted(srcFilePath, out password, out passwordTip);

            if (isEncrypted)
            {
                string result = null;
                foreach (UIElement ue in Globals.MainWindow.mainTabControl.Items)
                {
                    var edit = ue as MarkdownEditor;
                    if (edit != null && string.IsNullOrEmpty(edit.FullFilePath) == false &&
                        edit.FullFilePath.ToLower() == srcFilePath.ToLower())
                    {
                        if (edit.InputPasswordPanel.Visibility == Visibility.Collapsed)
                        {
                            result = edit.Password;
                        }
                    }
                }

                if (result == null)
                {
                    result = InputBox.Show(Globals.AppName, $"{passwordTip}", "", false, "注意：密码区分大小写。", true);
                }

                if (result == null)
                {
                    return "";
                }
                else
                {
                    if (result == password)
                    {
                        var allText = File.ReadAllText(srcFilePath);
                        contentText = SetPasswordPanel.TextDecrypt(allText.Substring(allText.IndexOf("\r\n") + 2).ToCharArray(), "DyBj#PpBb");
                    }
                    else
                    {
                        LMessageBox.Show("密码错误，无法编译此文件：\r\n" + srcFilePath, Globals.AppName,
                            MessageBoxButton.OK, MessageBoxImage.Warning);
                        if (srcFilePath.ToLower().EndsWith(".md"))
                        {
                            var htmlFilePath = srcFilePath.Substring(0, srcFilePath.Length - 3) + ".html";
                            if (File.Exists(htmlFilePath))
                            {
                                try
                                {
                                    File.Delete(htmlFilePath);
                                }
                                catch (Exception ex)
                                {
                                    LMessageBox.Show("无法删除已存在的 Html 文件：\r\n" + htmlFilePath + "\r\n" + "错误信息如下：\r\n" + ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                                    return null;
                                }
                            }
                            return null;
                        }
                        else return null;
                    }
                }
            }
            else
            {
                contentText = File.ReadAllText(srcFilePath);
            }

            // 必须提高合并行标记的级别
            var regexJoinLines1 = new Regex(@"[ 　]{2,}(((\r\n)|\n)[ 　\t]{0,}){0,}((\r\n)|\n)\^[ 　\t]{1,}", RegexOptions.Multiline);
            contentText = regexJoinLines1.Replace(contentText, "<para-splitter/>");

            var regexJoinLines2 = new Regex(@"(((\r\n)|\n)[ 　\t]{0,}){0,}((\r\n)|\n)\^[ 　\t]{1,}", RegexOptions.Multiline);
            contentText = regexJoinLines2.Replace(contentText, "");

            // 布局应该在合并行标记后面
            contentText = LayoutLineCompile(contentText);

            bool compilePageMenu;
            string headStyleTexts;
            //取出内部样式表文本。
            //CSS 样式表分外部样式表（用Link链接）、内部样式表（放在 Head 部分）、内联样式表（Inline）。
            //这里是指要放在 Html 页面的 Head 部分的样式表——它只作用于当前页面本身，由用户直接在文档中书写。

            string bodyEndScripts;
            //取出页面内手工写的脚本。
            //如果不提取， Markdown Sharp 会将这些脚本处理成普通段落文本，从而导致无效。

            string htmlBody = contentText;

            #region 调用用户编写的、指定为编译前自动调用的所有Python脚本

            ScriptEngine engine = Python.CreateEngine();
            ScriptScope scope = engine.CreateScope();

            htmlBody = CompileWithCustomPythonScripts(srcFilePath, directoryLevelMark, contentText, htmlBody, engine, scope);
            #endregion

            // 解决单行自定义折叠区（办法：将标头行末尾的 } 字符直接替换为 \r\n} 即可，其实甚是简单。）
            // 单行折叠区的规则： 首尾是 {}（不支持图标等），且头部必须有 折叠标记文本——这种情况下，标尾文本是空字符串。
            // 为什么不直接替换或者用正则？因为怕编写的正则表达式出问题导致误替换。

            var mlines = htmlBody.Split(new string[] { "\r\n", }, StringSplitOptions.None);
            var msb = new StringBuilder();
            var regSingleLineRegion = new Regex(@"^[ 　]{0,3}\{[ 　\t]{0,}?[\+\-]?[ 　\t]{0,}?(?=([:：]?[ 　\t]{0,}?[\[【][^\[\]【】]*[\]】]))");
            foreach (var mline in mlines)
            {
                var matchSingleLineRegion = regSingleLineRegion.Match(mline);
                if (matchSingleLineRegion.Success && mline.TrimEnd().EndsWith("}"))
                {
                    var temp = matchSingleLineRegion.Value.Replace(" ", "").Replace("　", "").Replace("\t", ""); ;
                    if (forEpub)
                    {
                        temp = mline.Substring(matchSingleLineRegion.Length).Trim(new char[] { ' ', '　', '\t', '}', });
                        if (temp.StartsWith("【】"))
                        {
                            temp = "【※】" + temp.Substring(2);
                        }
                        msb.Append($"<div class='region_epub'>" + temp + "</div>");
                        // 单行折叠区在 Epub 下效果太差，故直接改成这样。
                    }
                    else
                    {
                        if (temp.StartsWith("{+"))
                        {
                            msb.Append("{* " + mline.Substring(matchSingleLineRegion.Length).TrimEnd(new char[] { ' ', '　', '\t', '}', }) + "\r\n}\r\n");
                            // 其实就是“帮助”用户写上换行而已。这样就不必再动原始代码了。
                        }
                        else
                        {
                            msb.Append("{= " + mline.Substring(matchSingleLineRegion.Length).TrimEnd(new char[] { ' ', '　', '\t', '}', }) + "\r\n}\r\n");
                            // 其实就是“帮助”用户写上换行而已。这样就不必再动原始代码了。
                            // 注意：单行折叠区，意味着其主面板中无内容，所以须特别处理！！！
                        }
                    }
                }
                else
                {
                    msb.Append(mline + "\r\n");
                }
            }
            htmlBody = msb.ToString();

            // 将三个引号标记的原型选择题编译为LME编译器能识别的选择题
            htmlBody = CompilePrototypeChoiceExams(htmlBody);

            if (Globals.MainWindow.SupportThreeSlashQuotersCodeBlock)
            {
                // 编译三个反引号标记的代码块
                htmlBody = CompileSlashQuotersCodeBlocks(htmlBody);
            }

            if (Globals.MainWindow.EnableSetextHeaders)
            {
                htmlBody = CompileSetextHeaders(htmlBody);
            }
            else
            {
                htmlBody = CompileHeavyHorizontalLines(htmlBody);
            }

            // 尝试在每个文档的正文中寻找与 工作区中的条目的 标题文本 相同的文本片段，并尝试加上 <:xxx:> 这样的标记，以便自动创建链接。
            if (Globals.MainWindow.CreateLinksForTitles)
            {
                htmlBody = AutoCreateLinksToTitles.Execute(htmlBody, srcFilePath);
            }

            // 使用 <:xxx:> 引用脚注等均在这个方法中处理。
            htmlBody = FormatSpecialAnchorReferences(htmlBody, srcFilePath, forEpub);

            htmlBody = FormatAndCompileAutoNumberOfAnchors(htmlBody);

            htmlBody = CompileAnchors(htmlBody);

            #region 表格模拟的思维导图、时间轴图、列表等都要放到锚的后面处理！！
            // 将树型文字表编译成 table 模拟的思维导图（列表式）

            htmlBody = TimeAxis.CompileTimeAxises(ref md, htmlBody, forEpub);

            // 导出为 epub 时效果太差，无法使用。
            if (forEpub == false)
            {
                htmlBody = TreeMapCompiler._MapCompiler.CompileTreeMaps(htmlBody, ref md);
            }

            htmlBody = CompileTreeTableToList(htmlBody);
            #endregion 列表视图

            htmlBody = RemoveExtraLines(htmlBody, srcFilePath, ignoreNavAndSubLinks, out htmlDocumentTitle,
                out htmlDocumentPageHeader, out footerText, out compilePageMenu, out headStyleTexts, out bodyEndScripts,
                out List<string> importedCsses, out List<string> importedJavaScripts, out List<string> appendJavaScripts,
                out string htmlDocumentSubTitle, out string toWorkspacePrefix, forEpub, out string innerStyleText, out string testPaperId);

            if (Globals.MainWindow.CompilePageMenu)
                compilePageMenu = true;   // 强制编译出页面菜单

            if (htmlHeadersCollpase == MainWindow.HtmlHeadersCollapseType.No)
            {
                compilePageMenu = false;  // 页面菜单依赖于六级标题
            }

            if (withoutAdonors)
                compilePageMenu = false;    // 演示模式下，不添加此左栏菜单。

            if (forEpub)
            {
                compilePageMenu = false;   // Epub 电子书不支持 JavaScript 所以不必编译左边栏
            }

            bool forbiddenAutoNumber = true;

            if (presentateHtmlSplitterType != null)
            {
                if (presentateHtmlSplitterType.Value == PresentateHtmlSplitterType.TopLevelHeader)
                {
                    //当切分处理一个 Markdown 文件时，只有使用一级标题作为分割标准才能保证序号不出错。
                    //所以其它两种分割情况下，都必须
                    forbiddenAutoNumber = false;
                }
            }
            else forbiddenAutoNumber = false;//如果未传入这个值，说明不是在切分编译。

            // 自动生成 test_paper_id （如果用户没有设置）
            if (string.IsNullOrWhiteSpace(testPaperId) && Globals.MainWindow.CompileAsTestPaper)
            {
                var time = DateTime.Now;
                testPaperIdNumber++;
                testPaperId = time.Year.ToString("D4") + time.Month.ToString("D2") + time.Day.ToString("D2") + testPaperIdNumber.ToString("D2");
                LMessageBox.Show(
                    $"当前工作区开启了下列选项：\r\n\r\n" +
                    $"　　　【选择题按试卷样式编译】\r\n" +
                    $"　　　【接收选择题答案的网址】\r\n\r\n" +
                    $"　　但当前文档没有设置【试卷ID】。\r\n\r\n" +
                    $"　　继续编译将自动以【{testPaperId}】作为当前试卷的ID。\r\n\r\n" +
                    $"　　〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓\r\n\r\n" +
                    $"　　强烈建议手工在文档接近开头的位置按类似下列这样设置试卷ID值：\r\n\r\n" +
                    $"　　　；试卷ID：2023030202\r\n\r\n" +
                    $"　　这样不仅便于查看试卷ID，而且方便答案上传到服务器后比对答案等操作（您需要自己准备服务器并提供上传数据的接口）。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information, "AutoSetTestPaperID", null, Globals.MainWindow);
            }

            int choiceQuestionsCount = 0;
            int dbl_mark_index = 0;
            htmlBody = PreCompile(htmlBody, startH1Number, Globals.MainWindow.AutoNumberStyle, forbiddenAutoNumber, new char[] { '\n' },
                ref choiceQuestionsCount, ref dbl_mark_index, toWorkspacePrefix, forEpub, testPaperId, srcFilePath);

            // 自动创建指向标题的链接
            if (Globals.MainWindow.CreateLinksForTitles)
            {
                htmlBody = TryConvertTitlesToLink(srcFilePath, htmlBody);
            }

            if (string.IsNullOrEmpty(htmlDocumentTitle))
            {
                htmlDocumentTitle = ""; // 不再用 Markdown Document，允许用户去除标题！当标题为空时，自动隐藏标题区域。
            }

            //判断文件中是否还没有书写实际内容
            bool emptyDoc = false;    //初始默认值为false 是有道理的
            if (string.IsNullOrWhiteSpace(htmlBody))
            {
                emptyDoc = true;
            }
            else
            {
                emptyDoc = true;   //这里必须改为 true
                for (int i = 0; i < htmlBody.Length; i++)
                {
                    char c = htmlBody[i];
                    if (c == '\n' || c == ' ' || c == '\t' || c == '　') continue;
                    // 理论上，如果没有写内容，htmlBody只是会一连串的换行符(\n)组成的串。

                    emptyDoc = false;      //只要找到一个有意义的字符，文档就不是“空”的。
                    break;
                }
            }

            if (emptyDoc) htmlBody = "（此文档尚未添加内容）";

            // 必须“抢先”处理代码块，因为 MarkdownSharp 在对代码块的处理上有 Bug：
            //     当代码块写在两个 List 之间时，会出错。
            htmlBody = CompileCodeBlocks(htmlBody);

            htmlBody = md.Transform(htmlBody);

            var removeEmptyParagraphsRegex = new Regex(@"[ 　\t]*<p>[ 　\t]*</p>[ 　\t]*");
            htmlBody = removeEmptyParagraphsRegex.Replace(htmlBody, "");
            htmlBody = CustomMarkdownSupport.AppendCompile(htmlBody, out string footComments, directoryLevelMark,
                htmlHeadersCollpase, Globals.MainWindow.CompileCodeToFillBlank, compilePageMenu, (addBackToIndexLink ? srcFilePath : null),
                previewPageLink, nextPageLink, choiceQuestionsCount, forEpub, testPaperId, dbl_mark_index);
            //htmlBody = htmlBody.Replace("<p><div ", "<div ").Replace("</div></p>", "</div>");//去除不必要的空段。

            if (string.IsNullOrWhiteSpace(htmlBody))
            {
                return null;
            }

            //去除不必要的空段，但是要注意多个图标在一行的情况。
            htmlBody = Regex.Replace(htmlBody,
                @"<[pP]>[ 　\t]*((<[iI][mM][gG][^<]*/>)|(<[dD][iI][vV]>*.</[dD][iI][vV]>))[ 　\t]*</[pP]>",
                new MatchEvaluator(FormatParagraphsTag));

            if (string.IsNullOrEmpty(htmlBody) == false)
            {
                // 能不能支持实时切换主题并使用 Cookie 来保存用户的选择呢？
                // 理论上当然是可以的，现成的实现方案也有——利用 jQuery.cookie.js 可以方便地读、写cookie。
                // 但问题在于：1.实时切换主题必须要考虑填空题与主题配色的切换问题，这需要对原有的方案进行大幅度修改，风险太大；
                //             2.用 cookie 记录主题并再现，很容易出现 Html 预览选项卡上选定的主题与预览的效果相反的问题！！
                //               ——这势必让用户认为这是个Bug。
                // 所以综合考虑后，决定放弃这个功能的实现——这年头硬盘空间不值钱，编译两个主题的CHM文件也占不了多大空间。

                #region 链接主题CSS文件
                // 主题 CSS 文件由两个主题（共六个文件）构成。
                // 其中，基于演示模式的需要，主CSS文件分两个版本（演示版本、教程版本）；
                //       左边栏 CSS 文件则不分演示模式与普通教程模式。
                // 也就是说：
                //       presentation_light.css和lesson_light.css是不会同时使用的；
                //       并且 presentation_light.css不会用于编译工作区，更不会用在CHM文件中。
                // 此外，每套主题的三个CSS文件都支持用户自定义——只需要将自定义CSS文件命名为：
                //       “custom_{原文件名}”的形式即可。
                //       由于后添加的链接引用的CSS中定义的样式会覆盖先链接的CSS文件中定义的样式，
                //       所以这样不但可以实现用户自定义CSS样式，而且还并不需要用户实现每一个样式——便于用户微调页面效果。
                // 至于到底选择 presentation_light.css(lesson_light.css)还是presentation_dark.css(lesson_dark.css)，
                //       则是由“Globals.MainWindow.ThemeText”决定的，
                //       而“Globals.MainWindow.ThemeText”这个属性值由右工具栏“Html 预览”选项卡上的主题切换框的当前值决定。

                // 链接 主CSS文件
                string themeCssPrefix = (withoutAdonors ? "presentation_" : "lesson_");
                String themeCssLinkText = "<link id='theme_link' rel=\"stylesheet\" href=\"" + directoryLevelMark
                    + themeCssPrefix + Globals.MainWindow.ThemeText + ".css\" type=\"text/css\" />\n";

                // 链接用户自定义的 主CSS文件
                String customThemeCssLinkText = "";
                string customThemeCssFileName = "custom_" + themeCssPrefix + Globals.MainWindow.ThemeText + ".css";
                if (File.Exists(Globals.PathOfWorkspace + customThemeCssFileName))
                {
                    customThemeCssLinkText = "<link id='custom_theme_link' rel=\"stylesheet\" href=\"" + directoryLevelMark + customThemeCssFileName + "\" type=\"text/css\" />\n";
                }

                // 链接 左边栏菜单的CSS文件
                String menuThemeCssLinkText = "";
                if (forEpub == false)
                {
                    menuThemeCssLinkText = "<link id='menu_theme_link' rel=\"stylesheet\" href=\"" + directoryLevelMark
                        + "menu_" + Globals.MainWindow.ThemeText + ".css\" type=\"text/css\" />\n";
                }

                // 链接用户自定义的 左边栏菜单的CSS文件
                String customMenuThemeCssLinkText = "";
                if (forEpub == false)
                {
                    string customMenuThemeCssFileName = "custom_" + "menu_" + Globals.MainWindow.ThemeText + ".css";
                    if (File.Exists(Globals.PathOfWorkspace + customMenuThemeCssFileName))
                    {
                        customMenuThemeCssLinkText = "<link id='custom_menu_theme_link' rel=\"stylesheet\" href=\"" + directoryLevelMark + customMenuThemeCssFileName + "\" type=\"text/css\" />\n";
                    }
                }
                #endregion

                var jquery_file_name = "jquery-1.7.0.min.js";
                var easyui_import_text = "";
                if (Globals.MainWindow.EnableEasyUI)
                {
                    jquery_file_name = "_easyui~/jquery-1.8.0.min.js";
                    easyui_import_text = "<script type=\"text/javascript\" src=\"" + directoryLevelMark + "_easyui~/jquery.easyui.min.js\"></script>\r\n" +
                                         "<link href=\"" + directoryLevelMark + "_easyui~/themes/default/easyui.css\" rel=\"stylesheet\" type=\"text/css\"/>\r\n" +
                                         "<link href=\"" + directoryLevelMark + "_easyui~/themes/icon.css\" rel=\"stylesheet\" type=\"text/css\"/>\r\n" +
                                         "<script type=\"text/javascript\" src=\"" + directoryLevelMark + "_easyui~/locale/easyui-lang-zh_CN.js\"></script>\r\n";
                }

                string headScript = "<script type=\"text/javascript\" src=\"" + directoryLevelMark + jquery_file_name + "\"></script>" +
                                easyui_import_text +
                                "<script>\r\n" +
                                "var submitted=false;\r\n" +
                                "function ShowPage()\r\n" +
                                "{\r\n" +
                                "document.getElementById('content').style.visibility = \"visible\";\r\n" +
                                //$"document.body.style.zoom = {webBrowserZoom};\r\n" +
                                $"document.body.style.fontSize = {16 * webBrowserZoom}+'px';\r\n" +
                                //只有在这里想办法才可能解决IE 11下body元素向右偏移面能自动居中对齐的问题。
                                //网上找到的通过style等途径设置居中都无效。
                                //"var pageWidth = window.screen.availWidth ;" +
                                //"var bodyWidth = document.body.scrollWidth;" +
                                //"var leftMargin = (pageWidth - bodyWidth)/2;" +
                                //"document.getElementsByTagName('body')(0).style.marginLeft = leftMargin;" +
                                //"document.getElementsByTagName('body')(0).style.marginRight = leftMargin;" +
                                //"alert(\"ClientWidth\"+document.body.clientWidth+\"|OffsetWidth\"+document.body.offsetWidth+\"|scrollWidth\"+document.body.scrollWidth+\"|screenwidth\"+window.screen.width+\"|availWidth\"+window.screen.availWidth );" +
                                "}\r\n" +
                                "function HidePage()\r\n" +
                                "{\r\n" +
                                "document.getElementById('content').style.visibility = \"hidden\";\r\n" +
                                "}" +
                                "var theme_css_prefix = '" + themeCssPrefix + "';" +
                                "var path_prefix = '" + directoryLevelMark + "';" +
                                showImagesFunc +
                                hideIamgesFunc +
                                switchThemeFunc +
                                loadThemeFunc +
                                getThemeNameFunc +
                                loadUserSelectedThemeFunc +
                                "</script>\r\n";


                #region 利用 HighLight.js 来高亮代码块
                var highLightDefaultLink = "";
                //实测：即使是 utf-8 编码也会出问题。
                //if (Globals.MainWindow.DefaultEncoding == "utf-8")
                //{
                //    //测试得知 HightLight.Pack.Js 只支持 UTF-8 编码
                //    highLightDefaultLink = "<link rel=\"stylesheet\" href=\"" + directoryLevelMark + "Highlight~/styles/default.css\" type=\"text/css\">\n";
                //    //script += "<script src=\"" + directoryLevelMark + "Highlight~/highlight.pack.js\"></script>\n";
                //    //script += "<script>hljs.initHighlightingOnLoad();</script>\n";
                //    //这两行要添加在引用 jQuery.js 之后。
                //}
                #endregion

                string timeText = "";
                if (Globals.MainWindow.AppendTimeOfCompiling)
                {
                    timeText = "<p id =\"compile_time\">" + DateTime.Now.ToString() + "</p>";
                }

                string pagefooterString = "<div class=\"foot\"><p id=\"author\">" + footerText //这是文档的脚注文本，一般可以填组织、作者名称什么的。
                    + $"</p>{timeText}</div>";

                var charset = Globals.MainWindow.DefaultEncoding;//"gb2312";//"utf-8";

                var indexOrHomePageLinkText = BuildIndexOrHomePageLink(srcFilePath, out string indexPath);

                var importedCssText = "";
                foreach (var lt in importedCsses)
                {
                    importedCssText += (lt + "\r\n");
                }

                var importedJavaScriptText = "";
                foreach (var jt in importedJavaScripts)
                {
                    importedJavaScriptText += (jt + "\r\n");
                }

                var appendJavaScriptText = "";
                foreach (var ajt in appendJavaScripts)
                {
                    appendJavaScriptText += (ajt + "\r\n");
                }

                var askNames = Globals.MainWindow.AskNamesArrayScript;
                //应形如：
                //var listSrc = ['张三', '李四', '王二麻子', '钱多多', '金百万', '张三丰', '张天师', '孔夫子', '玉皇'];
                //var list = ['张三', '李四', '王二麻子'];
                var askPanelScript =
@"
function OnClick(count){
    if(count > list.length){
      var listCopy = list.concat([]);        // 不然sort()方法会影响原数组，所以这里用深拷贝  
      var listSrcCopy = listSrc.concat([]);  // 不然sort()方法会影响原数组，所以这里用深拷贝
      if(listCopy.sort().toString() == listSrcCopy.sort().toString()){
        alert('名册中只有 ' + list.length + ' 人。提问人数大于名册中总人数!');
        return;
      }
    }
    var panel = document.getElementById('names_cell');
    panel.innerHTML = '';
    for (var i = 0; i < count; i++)
    {
        // 获取随机数
        var rand = Math.floor(Math.random() * list.length);
        // var data = list.slice(rand, 1)[0];
        // 随机从数组中取出某值（不会改变原数组）
        // 随机从数组中取出某值（会改变原数组）
        var data = list.splice(rand, 1)[0];
        if (data == undefined)
        {
            list = [];  // 先清空,不然会出现同名现象
            list = list.concat(listSrc);
            data = list.splice(rand, 1)[0];
        }
        panel.innerHTML += '<li>' + data + '</li>';
    }
}
function showAskPanel()
{
    var s = document.getElementById('ask_base_panel');
    s.style.display = 'block';

    var l = document.getElementById('ask_panel');
    l.style.display = 'block';
}
function hideAskPanel()
{
    var s = document.getElementById('ask_base_panel');
    s.style.display = 'none';

    var l = document.getElementById('ask_panel');
    l.style.display = 'none';
}";
                var popupPanelScript =
@"<!--showPopupText()的引用方式：-->
<!--<a href='javascript:void(0);' onclick='showPopupText(this);' link-to='fc1' popup-height='25%'>链接</a>-->
function showPopupText(a_element)
{
    if(a_element === null){
        alert('未传入 a 元素对象！本函数应在 a 元素的 onclick 元素调用！');
        return;
    }
    var s = document.getElementById('popup_base_panel');
    if(s === null){
        alert('未找到 popup_base_panel 对象！');
        return;
    }
    s.style.display = 'block';
    var l = document.getElementById('popup_panel');
    if(l === null){
        alert('未找到 popup_panel 对象！');
        s.style.display = 'none';
        return;
    }
    var height = $(a_element).attr('popup-height');
    if(height != null && height != ''){
        l.style.height = height;
    }else{
        l.style.height = '25%';
    }

    var poptxt = document.getElementById('popup_txt');
    if(poptxt === null){
        alert('未找到 popup_txt 对象！');
        return;
    }

    var popdiv = document.getElementById('popup_div');
    if(popdiv === null){
        alert('未找到 popup_div 对象！');
        return;
    }else{
        popdiv.style.display = 'none';
    }

    // a_element 是 <a> 元素，其 link-to 属性指向某个隐藏的 div。
    var id = $(a_element).attr('link-to');
    if(id == null || id == ''){
        alert('未能从 a 元素获取 link-to 属性值！');
        s.style.display = 'none';
        return;
    }
    var hidediv = document.getElementById(id);
    if(hidediv === null){
        alert('未找到 Id 为 ' + id + '的对象！');
        s.style.display = 'none';
        return;
    }
    var title = $(a_element).attr('title');
    if(title == null || title == undefined || title == ''){
        var id = $(a_element).attr('link-to');
        if(id != null || id != ''){
            var destElement = document.getElementById(id);
            if(destElement != null){
                var altText = $(destElement).attr('alt');
                if(altText != null && altText != undefined && altText.length > 0){
                    poptxt.innerHTML = altText;
                } else {
                    poptxt.innerHTML = destElement.innerHTML;
                }
            } else {
                poptxt.innerHTML = a_element.innerHTML;  // 脚注的内容在 title 属性里，如果没有这个属性值，则显示字面值
            }
        } else {
            poptxt.innerHTML = a_element.innerHTML;  // 脚注的内容在 title 属性里，如果没有这个属性值，则显示字面值
        }
    } else {
        poptxt.innerHTML = title;  // 脚注的内容在 title 属性里
    }
    poptxt.style.cssText = '';  // 先重置
    poptxt.style.display = 'block';   // 这个不能继承，否则无法显示。
    l.style.display = 'block';
}
/** * 判断是否是IE * @returns boolean */
function isIE() { 
    if (!!window.ActiveXobject || ""ActiveXObject"" in window) { 
        return true;
    } else { 
        return false;
    }
}
/** * 判断是否是IE11 * @returns boolean */
function isIE11(){ 
    if((/Trident\/7\./).test(navigator.userAgent)) { 
        return true;
    } else { 
        return false;
    }
}
<!--showPopupPanel()的引用方式：-->
<!--<a href='javascript:void(0);' onclick='showPopupPanel(this);' link-to='hd1' popup-height='25%'>链接</a>-->
function showPopupPanel(a_element)
{
    if(a_element === null){
        alert('未传入 a 元素对象！本函数应在 a 元素的 onclick 元素调用！');
        return;
    }
    var s = document.getElementById('popup_base_panel');
    if(s === null){
        alert('未找到 popup_base_panel 对象！');
        return;
    }
    s.style.display = 'block';
    var l = document.getElementById('popup_panel');
    if(l === null){
        alert('未找到 popup_panel 对象！');;
        s.style.display = 'none';
        return;
    }
    var width = $(a_element).attr('popup-width');
    if(width != null && width != ''){
        l.style.width = width;
    }else{
        l.style.height = '80%';  // 默认为宽度的 80%
    }
    var height = $(a_element).attr('popup-height');
    if(height != null && height != ''){
        l.style.height = height;
    }else{
        l.style.height = '80%';  // 默认为高度的 80%
    }
    var poptxt = document.getElementById('popup_txt');
    if(poptxt === null){
        alert('未找到 popup_txt 对象！');
        return;
    }else{
        poptxt.style.display = 'none';
    }
    var popdiv = document.getElementById('popup_div');
    if(popdiv === null){
        alert('未找到 popup_div 对象！');
        s.style.display = 'none';
        return;
    }

    // a_element 是 <a> 元素，其 link-to 属性指向某个隐藏的 div。
    var id = $(a_element).attr('link-to');
    if(id == null || id == ''){
        alert('未能从 a 元素获取 link-to 属性值！');
        s.style.display = 'none';
        return;
    }
    var destBlockElement = document.getElementById(id);
    if(destBlockElement === null){
        alert('未找到 Id 为 ' + id + '的对象！');
        s.style.display = 'none';
        return;
    }
    var destElementTag = destBlockElement.tagName.toLowerCase();
    popdiv.style.cssText = '';  // 先重置
    
    // 此方法不好，无法显示自定义折叠区的动态折叠/展开效果。
    // if(destElementTag == 'div'){
    //     popdiv.style.cssText = destBlockElement.style.cssText;
    //     popdiv.innerHTML = destBlockElement.innerHTML;
    // } else {
    //     popdiv.innerHTML = destBlockElement.outerHTML;
    //    // 不必担心非 div 的 style.display 值为 none；
    //    // 只有 div 才可能被隐藏。
    // }
    // 改成用纯 JS 移动元素到弹出层
    var children = popdiv.children;
    if(children.length > 0){
        var currentElement = popdiv.children[0];
        var currentElementId = currentElement.getAttribute('id');
        if(currentElementId != undefined){
            var current_place_holder_id = currentElementId + '_place_holder';
            var current_place_holder = document.getElementById(current_place_holder_id);
            if(current_place_holder != undefined){
                var parent = current_place_holder.parentElement;
                if(parent != undefined){
                    parent.insertBefore(currentElement, current_place_holder);
                    if (isIE() || isIE11()) { 
                        current_place_holder.removeNode(true);
                    } else { 
                        current_place_holder.remove();
                    }
                }
            }
        }
    }
    var place_holder_element = document.createElement(""div"");
    var destBlockElementId = destBlockElement.getAttribute('id');
    if(destBlockElementId == undefined || destBlockElementId == ''){
        destBlockElementId = 'slide_' + index;
        destBlockElement.setAttribute('id', destBlockElementId);
    }
    place_holder_element.setAttribute('id', destBlockElementId + '_place_holder');
    destBlockElement.parentElement.insertBefore(place_holder_element, destBlockElement);
    if (isIE() || isIE11()) {
        destBlockElement.removeNode(true);
    } else {
        destBlockElement.remove();
    }
    popdiv.appendChild(destBlockElement);

    popdiv.style.display = 'block';   // 这个不能继承，否则无法显示。
    l.style.display = 'block';
}
function hidePopupPanel()
{
    var s = document.getElementById('popup_base_panel');
    if(s === null){
        alert('未找到 popup_base_panel 对象！');
        return;
    }
    s.style.display = 'none';

    var l = document.getElementById('popup_panel');
    if(l === null){
        alert('未找到 popup_panel 对象！');
        return;
    }
    l.style.display = 'none';

    var slide_control_panel = document.getElementById('slide_control_panel');
    if(slide_control_panel != null){
        slide_control_panel.style.display = 'none';
    }
    
    var slide_handler = document.getElementById('slides_handler');
    if(slide_handler != null){
        if(slides.length > 0){
            slides_handler.style.display = 'block';
        }else{
            slides_handler.style.display = 'none';
        }
    }
}
$(document).ready(function(){
		$('#ask_panel_close_button').hover(function(){
			$('#ask_panel_close_button').css('background-color', 'lightgray');
		},function(){
			$('#ask_panel_close_button').css('background-color', 'transparent');
		});
});
$(document).ready(function(){
		$('#popup_panel_close_button').hover(function(){
			$('#popup_panel_close_button').css('background-color', 'lightgray');
		},function(){
			$('#popup_panel_close_button').css('background-color', 'transparent');
		});
});
var indexOfSlide = -1;
var slides;   //  jQuery 对象数组
$('document').ready(
    function getSlides(){
         slides = $('.slide');
         var trigger = document.getElementById('slides_trigger');
         if(trigger != null && trigger != undefined){
             if(slides.length > 0){
                 $(trigger).show();
             }else{
                 $(trigger).hide();
             }
         }
    }
);
function showFirstSlide(a_element){
    if(slides.length > 0){
        showSlide(a_element, 0, false);
    }
}
function showPreviewSlide(a_element){
    if(slides.length < 2) return;
    if(indexOfSlide <= 0) return;
    showSlide(a_element, indexOfSlide - 1, false);
}
function showNextSlide(a_element){
    if(slides.length < 2) return;
    if(indexOfSlide >= slides.length - 1)return;
    showSlide(a_element, indexOfSlide + 1, true);
}
function showLastSlide(a_element){
    if(slides.length > 0){
        showSlide(a_element, slides.length - 1, true);
    }
}
function showCurrentSlide(a_element) {
    if(slides == null || slides.length <= 0) {
        alert('没有可演示的幻灯片。');
        return;
    }
    if(indexOfSlide < 0) {
        indexOfSlide += 1;
    }
    if(indexOfSlide >= slides.length - 1) {
        indexOfSlide = slides.length - 1;
    }
    showSlide(a_element, indexOfSlide, true);
}
function showSlide(a_element, index, is_toend) {
    var slide_panel = document.getElementById('slide_control_panel');
    if (slide_panel == null) {
        alert('未找到幻灯片控制面板。');
        return;
    }
    $(slide_panel).show();
    if (a_element === null) {
        alert('未传入 a 元素对象！本函数应在 a 元素的 onclick 元素调用！');
        return;
    }
    var popup_base_panel = document.getElementById('popup_base_panel');
    if (popup_base_panel === null) {
        alert('未找到 popup_base_panel 对象！');
        return;
    }
    popup_base_panel.style.display = 'block';
    var popup_panel = document.getElementById('popup_panel');
    if (popup_panel === null) {
        alert('未找到 popup_panel 对象！');;
        popup_base_panel.style.display = 'none';
        return;
    }
    var width = $(a_element).attr('popup-width');
    if (width != null && width != '') {
        popup_panel.style.width = width;
    } else {
        popup_panel.style.height = '80%'; // 默认为宽度的 80%
    }
    var height = $(a_element).attr('popup-height');
    if (height != null && height != '') {
        popup_panel.style.height = height;
    } else {
        popup_panel.style.height = '80%'; // 默认为高度的 80%
    }

    var poptxt = document.getElementById('popup_txt');
    if(poptxt === null){
        alert('未找到 popup_txt 对象！');
        return;
    }else{
        poptxt.style.display = 'none';
    }

    var popdiv = document.getElementById('popup_div');
    if (popdiv === null) {
        alert('未找到 popup_div 对象！');
        popup_base_panel.style.display = 'none';
        return;
    }

    var destBlockElement = slides[index];
    if (destBlockElement === null) {
        alert('未找到 index 为 ' + index + '的对象！');
        popup_base_panel.style.display = 'none';
        return;
    }

    $(popdiv).fadeOut();

    // 此方法不能显示自定义折叠区动态折叠效果
    // var destElementTag = destBlockElement.tagName.toLowerCase();
    // popdiv.style.cssText = ''; // 先重置
    // if (destElementTag == 'div') {
    //     popdiv.style.cssText = destBlockElement.style.cssText;
    //     popdiv.innerHTML = destBlockElement.innerHTML;
    // } else {
    //     popdiv.innerHTML = destBlockElement.outerHTML;
    //     // 不必担心非 div 的 style.display 值为 none；
    //     // 只有 div 才可能被隐藏。
    // }
    // 改成用纯 JS 移动元素到弹出层
    var children = popdiv.children;
    if(children.length > 0){
        var currentElement = popdiv.children[0];
        var currentElementId = currentElement.getAttribute('id');
        if(currentElementId != undefined){
            var current_place_holder_id = currentElementId + '_place_holder';
            var current_place_holder = document.getElementById(current_place_holder_id);
            if(current_place_holder != undefined){
                var parent = current_place_holder.parentElement;
                if(parent != undefined){
                    parent.insertBefore(currentElement, current_place_holder);
                    if (isIE() || isIE11()) { 
                        current_place_holder.removeNode(true);
                    } else { 
                        current_place_holder.remove();
                    }
                }
            }
        }
    }
    var place_holder_element = document.createElement(""div"");
    var destBlockElementId = destBlockElement.getAttribute('id');
    if(destBlockElementId == undefined || destBlockElementId == ''){
        destBlockElementId = 'slide_' + index;
        destBlockElement.setAttribute('id', destBlockElementId);
    }
    place_holder_element.setAttribute('id', destBlockElementId + '_place_holder');
    destBlockElement.parentElement.insertBefore(place_holder_element, destBlockElement);
    if (isIE() || isIE11()) {
        destBlockElement.removeNode(true);
    } else {
        destBlockElement.remove();
    }
    popdiv.appendChild(destBlockElement);

    // popdiv.style.display = 'block'; // 这个不能继承，否则无法显示。
    // 改成用 jQuery
    // if(is_toend){  // 机器性能不太高的时候这个效果很差，但不加动画又显示不出来。
    //     $(popdiv).slideDown();
    // }else{
    //     $(popdiv).fadeIn();
    // }
    $(popdiv).fadeIn();
    popup_panel.style.display = 'block';

    var slide_handler = document.getElementById('slides_handler');
    if(slide_handler != null){
        slides_handler.style.display = 'none';
    }

    indexOfSlide = index;
}
";

                var askPanelElement =
$@"<img id='ask_handler' onclick='javascript:showAskPanel()' src='{directoryLevelMark}Images~/ask_panel_handler.png' alt='提问'/>
  <div id='ask_base_panel' onclick='hideAskPanel();'></div>  
  <div id='ask_panel'>   
    <p id='close_ask_panel_p'><img id='ask_panel_close_button' onclick='javascript:hideAskPanel()' src='{directoryLevelMark}Images~/ask_panel_close.png' style='margin-right:0;margin-top:0;' alt='关闭'/></p>            
      <table id = 'ask_buttons_table'>                
      <tr><td><input type='button' onclick='OnClick(1)' value='提问 1 位'></input></td></tr>
      <tr><td><input type='button' onclick='OnClick(2)' value='提问 2 位'></input></td></tr>
      <tr><td><input type='button' onclick='OnClick(3)' value='提问 3 位'></input></td></tr>
      <tr><td><input type='button' onclick='OnClick(4)' value='提问 4 位'></input></td></tr>
      <tr><td><input type='button' onclick='OnClick(5)' value='提问 5 位'></input></td></tr>
      </table>
      <ol id='names_cell'></ol>
  </div>";

                var popupPanelElement =
$@"<div id='popup_base_panel' onclick='hidePopupPanel();'></div>  
  <div id='popup_panel'>
    <p id='close_popup_panel_p'><img id='popup_panel_close_button' onclick='javascript:hidePopupPanel()' src='{directoryLevelMark}Images~/popup_panel_close.png' style='margin-right:0;margin-top:0;' alt='关闭'/></p>            
    <div>
        <p id='pop_name_para'><span class='pop_name_span_plholder'>&lt;</span><span id='pop_name_span'></span><span class='pop_name_span_plholder'>&gt;</span></p>
        <div id='popup_txt'></div>
        <div id='popup_div'></div>
    </div>
  </div>";
                var slideAskButtonText = "";
                if (string.IsNullOrWhiteSpace(Globals.MainWindow.AskNames) == false)
                {
                    slideAskButtonText += @"
<script>
function AskSomeone(count){
    if(count > list.length){
      var listCopy = list.concat([]);        // 不然sort()方法会影响原数组，所以这里用深拷贝  
      var listSrcCopy = listSrc.concat([]);  // 不然sort()方法会影响原数组，所以这里用深拷贝
      if(listCopy.sort().toString() == listSrcCopy.sort().toString()){
        alert('名册中只有 ' + list.length + ' 人。提问人数大于名册中总人数!');
        return;
      }
    }
    var pns = document.getElementById('pop_name_span');
    pns.innerHTML = '';
    for (var i = 0; i < count; i++)
    {
        // 获取随机数
        var rand = Math.floor(Math.random() * list.length);
        // var data = list.slice(rand, 1)[0];
        // 随机从数组中取出某值（不会改变原数组）
        // 随机从数组中取出某值（会改变原数组）
        var data = list.splice(rand, 1)[0];
        if (data == undefined)
        {
            list = [];  // 先清空,不然会出现同名现象
            list = list.concat(listSrc);
            data = list.splice(rand, 1)[0];
        }
        pns.innerHTML += '&lt;&lt;&lt;&#32;&#32;&#32;' + data + '&#32;&#32;&#32;&gt;&gt;&gt;';
    }
}
</script>";
                    slideAskButtonText += "\r\n    <a id='slide_ask_button' href='javascript:void(0);' onclick='AskSomeone(1);'></a>\r\n";
                }
                var slidePanelElement =
$@"
<div id='slide_control_panel'>
    {slideAskButtonText}
    <a id='slide_first_button' href='javascript:void(0);' onclick='showFirstSlide(this);'></a>
    <a id='slide_preview_button' href='javascript:void(0);' onclick='showPreviewSlide(this);'></a>
    <a id='slide_next_button' href='javascript:void(0);' onclick='showNextSlide(this);'></a>
    <a id='slide_last_button' href='javascript:void(0);' onclick='showLastSlide(this);'></a>    
</div>
";

                // 当“临时禁止向文档中添加 JS 脚本”菜单项被勾选时，不需要添加这些元素。
                if (Globals.MainWindow.miRemoveScripts.IsChecked == true)
                {
                    askNames = "";
                    askPanelElement = "";
                    askPanelScript = "";
                }

                string htmlLeader;

                if (forEpub == false)
                {
                    htmlLeader = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
                                + "\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\" >\r\n"   //测试用：style=\"background:red;\"                                
                                + "<!-- saved from url=(0016)http://localhost -->\r\n"     //旧版：+ "<!-- saved from url=(0014)about:internet -->\n"
                                + "<head>\r\n"
                                + $"<meta http-equiv=\"X-UA-Compatible\" content=\"IE={Globals.MainWindow.DefaultIeVersion}\"/>\r\n"
                                // 注意：某些 jQuery 方法在 IE 版本较低（例如 8）、且编码方式不是 UTF-8 的情况下无法执行！！！
                                // + "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"/>\r\n"  //IE=edge 是装了什么版本就用什么版本。
                                // 这行是指定此网页应使用什么版本的 IE 内核进行渲染。
                                // 默认情况下，hh.exe 使用的 IE 内核很老，存在一些问题，比如不支持圆角效果。
                                // 又比如代码块出现横向滚动条时，会占据纵向空间，导致看不到最后一行文本。
                                // 这一问题在代码块中只有一行文本时尤其突出——只能看到半行，十分不便。
                                // 经测试，在IE 8 中已经解决了此问题。但 IE 9 或更新的内核与 hh.exe 的结合又不好，界面不协调。
                                // 所以折衷考虑，如果有编译为 CHM 文件的需求（通常应是 GB2312 编码），就指定 IE 8 内核；
                                // 而如果不准备编译 CHM 文件，则使用默认安装的最高版本内核。
                                // ——前提条件，用户的计算机上要安装 IE 8 ——这是 WinXP 支持的最高版本的 IE。
                                + "<title>\r\n"
                                + htmlDocumentTitle
                                + "\r\n</title>\r\n"
                                + "<meta name=\"info\" content=\""
                                + shortFileName
                                + "\" />\r\n"
                                + "<meta name=\"viewport\" content=\"width=device-width, user-scalable=yes\" />\r\n"
                                + "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=" + charset + "\" />\r\n"
                                + themeCssLinkText
                                + highLightDefaultLink
                                + customThemeCssLinkText         //之所以在用户自定义CSS文件存在时仍然引用LME提供的CSS是防止用户编写的CSS有问题。保留LME的CSS可以减少错误的概率。
                                + importedCssText
                                + (compilePageMenu ? (menuThemeCssLinkText + customMenuThemeCssLinkText) : "")   //注意linkCss2c和linkCss3c的顺序不能在前。
                                + headStyleTexts
                                + (string.IsNullOrWhiteSpace(innerStyleText) ? "" : innerStyleText)
                                + headScript
                                + importedJavaScriptText
                                + (string.IsNullOrWhiteSpace(Globals.MainWindow.AskNames) ? "" : ("<script>" + askNames + askPanelScript + "</script>"))
                                + $"<script>{popupPanelScript}</script>"
                                //旧版（测试）：+ "</head><body onLoad=\"HidePage()\" style=\"background:green;\">"//overflow-y:hidden; scroll=\"no\"//隐藏垂直滚动条不好。
                                + "</head>\r\n";
                }
                else
                {
                    htmlLeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                               + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//ZH\""
                               + "  \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"
                               + "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
                               + "<head>"
                               + "  <title>" + htmlDocumentTitle + "</title>"
                               + "  <link href=\"../_epub.css\" type=\"text/css\" rel=\"stylesheet\"/>"
                               //                           电子书不应支持动态效果、深色背景等，故 menu_...css 也不需要。
                               //                   ★注意：导出电子书时会将工作区根目录下的 _epub.css 复制到 Epub 临时文件夹里。
                               + "  <link href=\"../custom_lesson_light.css\" type=\"text/css\" rel=\"stylesheet\"/>"
                               + "</head>\r\n";
                }

                //解决总是浏览网页时总是显示“为保护……你的 Web 浏览器已经限制此文件……”警示信息条。
                //<!-- saved from url=(0014)about:internet -->必须在Html之后Head之前，否则Microsoft Edge中无法正确显示。
                //<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
                string htmlDocumentText = htmlLeader
                                + "<body" + (withoutAdonors ? " style=\"overflow-x:auto;\"" : "") + (Globals.MainWindow.miRemoveScripts.IsChecked ? "" : " onLoad=\"ShowPage()\"") + ">\r\n"
                                + "<p style=\"visibility: collapse;\"><span id=\"__TOP_4E4ABC53-B143-46FF-93CF-F9381EAD8E14__\" class=\"anchor\"></span></p>"       //这个锚总是存在的。
                                + (withoutAdonors ? "" : "<div style=\"text-align:right\"><span class=\"pageheader\">")
                                + (withoutAdonors ? "" : htmlDocumentPageHeader)
                                + (withoutAdonors ? "" : "</span><a onclick=\"print()\" style=\"visibility: hidden\">"/*打印〔此功能会丢失内容〕...*/+ "</a></div>")
                                + "<div id=\"printArea\">"
                                + ((withoutAdonors && forEpub == false) ? "" : ((string.IsNullOrEmpty(htmlDocumentTitle) || htmlDocumentTitle.StartsWith("!!")) ? "<p class='fileheader' id='file_header' style='display:none;'>" : "<p class='fileheader' id='file_header'>"))
                                + ((withoutAdonors && forEpub == false) ? "" : htmlDocumentTitle)
                                //这是整个文档的标题，以 % 开头。
                                + ((withoutAdonors && forEpub == false) ? "" : (string.IsNullOrWhiteSpace(htmlDocumentSubTitle) ? "" : ($"<p class='subtitle' id='sub_title'>——{htmlDocumentSubTitle}</p>")))
                                + ((withoutAdonors && forEpub == false) ? "" : (Globals.MainWindow.AppendHeadLine ? "</p><hr class=\"headerline\"/>" : ""))
                                //这文档的副标题，以 %—— 开头
                                + "<div id=\"content\">"
                                + htmlBody
                                + "<script></script>"
                                + (compilePageMenu ? "<p class=\"back_to_top_link\"><a href=\"#__TOP_4E4ABC53-B143-46FF-93CF-F9381EAD8E14__\">回到顶部</a></p>" : "")
                                + "</div>"
                                + ((withoutAdonors && forEpub == false) ? "" : (Globals.MainWindow.AppendFootLine ? "<hr class=\"footline\"/>" : ""))
                                + "<div class=\"foot-comment-area\">" + footComments + "</div>"
                                + (withoutAdonors ? "" : pagefooterString)
                                + (addBackToIndexLink ? ("<div id=\"lme_nav_panel\" class=\"lme_nav_panel_cls\"><p style=\"margin-left:10px;margin-right:10px;text-indent:0px;\"><span id=\"lme_nav_header\" class=\"lme_nav_header_cls\"></span>" + previewPageLink + " " + indexOrHomePageLinkText + " " + nextPageLink + "</p></div>") : "")
                                + "</div>"
                                + $"\r\n{ShortCutScript(previewPagePath, indexPath, nextPagePath)}{bodyEndScripts}\r\n<p style='text-indent:0;'>"
                                //+ $"<a href='javascript:SwitchTheme();'>[明|暗]</a>"
                                + $"<div>" + ((Globals.MainWindow.miRemoveScripts.IsChecked == false && Globals.MainWindow.AppendThemeSwitcher && forEpub == false) ? $"<img id=\"theme_switcher\" src=\"{directoryLevelMark}Images~/theme_switcher.png\" onclick=\"SwitchTheme()\"/>" : "") + "</div>"
                                + (forEpub ? "" : slidePanelElement)
                                + $"<div id='slides_trigger'>" + ((Globals.MainWindow.miRemoveScripts.IsChecked == false && forEpub == false) ? $"<img id=\"slides_handler\" src=\"{directoryLevelMark}Images~/_slide/slides_handler.png\" onclick=\"showCurrentSlide(document.getElementById('slide_next_button'));\"/>" : "") + "</div>"
                                + "</p>"
                                + ((string.IsNullOrWhiteSpace(Globals.MainWindow.AskNames) || forEpub || string.IsNullOrWhiteSpace(askNames)) ? "" : askPanelElement)    // 这个不能放前面,不然会在选择题选项底层,导致误点选项
                                + (forEpub ? "" : popupPanelElement)                                              // 导出 epub 电子书时不支持脚本，也不需要这些对应元素
                                + "<script>$(document).ready(LoadUserSelectedTheme());</script>"
                                + appendJavaScriptText
                                + "</body></html>";

                htmlTitle = htmlDocumentTitle;

                htmlDocumentText = htmlDocumentText.Replace("<para-splitter/>", "</p><p>");

                //对编译后的html格式化。
                if (Globals.MainWindow.FormatAfterCompile || forEpub)
                {
                    htmlDocumentText = FormatHtml(htmlDocumentText, originalMarkdownFilePath, forEpub, mdFilesList, false);
                }
                else
                {
                    htmlDocumentText = FormatHtml(htmlDocumentText, originalMarkdownFilePath, forEpub, mdFilesList, true);
                }

                //统一行尾结束符。
                htmlDocumentText = htmlDocumentText.Replace("\r\n", "\n").Replace("\n", "\r\n");

                htmlDocumentText = ChangeUTF8Space(htmlDocumentText);

                #region 调用用户编写的、指定为编译后自动调用的所有Python脚本
                //如果用户编写了指定为“编译后自动调用”的Python脚本文本。
                ScriptEngine engine2 = Python.CreateEngine();
                ScriptScope scope2 = engine2.CreateScope();

                try
                {
                    var list = new List<UIElement>();
                    foreach (var item in Globals.MainWindow.lbxPythonScripts.Items)
                    {
                        var ue = item as UIElement;
                        if (ue != null) list.Add(ue);
                    }

                    foreach (var item in Globals.MainWindow.lbxWorkspacePythonScripts.Items)
                    {
                        var ue = item as UIElement;
                        if (ue != null) list.Add(ue);
                    }

                    foreach (var item in list)
                    {
                        var psli = item as PythonScriptListItem;
                        if (psli == null || psli.CallMode != PythonScriptCallMode.AfterCompiledToHtml) continue;

                        var sourceCode2 = engine2.CreateScriptSourceFromFile(psli.FullPath);
                        // 运行脚本
                        scope2.SetVariable("inputText", htmlDocumentText);
                        scope2.SetVariable("outputText", htmlDocumentText);
                        scope.SetVariable("documentText", htmlDocumentText);
                        scope.SetVariable("ignoreTextChanging", false);
                        scope2.SetVariable("pathToWorkspace", directoryLevelMark);
                        scope.SetVariable("pathOfWorkspace", Globals.PathOfWorkspace);

                        // 传递键盘功能键状态
                        L.GetKeyboardState(out bool isAlt, out bool isShift, out bool isCtrl);
                        scope.SetVariable("isCtrl", isCtrl);
                        scope.SetVariable("isShift", isShift);
                        scope.SetVariable("isAlt", isAlt);

                        var fi = new FileInfo(srcFilePath);
                        scope.SetVariable("psArgs", new PythonScriptArgs()
                        {
                            MdFileInfo = fi,
                        });
                        // 重设搜索路径，默认的不管用
                        var pathList = engine.GetSearchPaths();
                        pathList.Add(Globals.PathOfPythonScripts);
                        pathList.Add(Globals.PathOfWorkspacePythonScripts);
                        pathList.Add(Globals.InstalledPath);
                        engine.SetSearchPaths(pathList);

                        scope2.ImportModule("sys");
                        scope2.ImportModule("clr");

                        sourceCode2.Execute<string>(scope2);
                        var text = scope2.GetVariable<string>("outputText");
                        htmlDocumentText = Utils.Text.FormatLineSplitter(text);
                    }
                }
                catch (Exception ex)
                {
                    ExceptionOperations eo = engine2.GetService<ExceptionOperations>();
                    string error = eo.FormatException(ex);
                    LMessageBox.Show(error, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    htmlDocumentText = contentText;
                }
                #endregion

                //在必要的情况下，删除所有 JS 脚本
                if (Globals.MainWindow.miRemoveScripts.IsChecked)
                {
                    var regScripts = new Regex(@"<script");
                    htmlDocumentText = Regex.Replace(htmlDocumentText, @"(?is)<script[^>]*?>.*?</script>", "", RegexOptions.IgnoreCase);
                    Globals.MainWindow.tbCompileInfo.Text = "已开启【临时禁止网页中添加 JS 脚本】，相关功能将失效！";
                    Globals.MainWindow.tbCompileInfo.Visibility = Visibility.Visible;
                }
                else
                {
                    Globals.MainWindow.tbCompileInfo.Text = "";
                    Globals.MainWindow.tbCompileInfo.Visibility = Visibility.Collapsed;
                }

                try
                {
                    if (charset == "gb2312")
                    {
                        using (StreamWriter stream = new StreamWriter(newFileName, false, System.Text.Encoding.GetEncoding("gb2312")))
                        {
                            stream.Write(htmlDocumentText);
                        }
                    }
                    else if (charset == "gb18030")
                    {
                        using (StreamWriter stream = new StreamWriter(newFileName, false, System.Text.Encoding.GetEncoding("gb18030")))
                        {
                            stream.Write(htmlDocumentText);
                        }
                    }
                    else  //其余全部强制为 utf-8
                    {
                        using (StreamWriter stream = new StreamWriter(newFileName, false, Encoding.UTF8))
                        {
                            stream.Write(htmlDocumentText);
                        }
                    }
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                    return null;
                }

                return newFileName;
            }

            return null;
        }

        /// <summary>
        /// 如果用户编写了指定为“编译前自动调用”的Python脚本文本。
        /// </summary>
        /// <param name="srcFilePath">文件路径。</param>
        /// <param name="directoryLevelMark">文件所在的目录层级。</param>
        /// <param name="contentText">传入时与 htmlBody 参数值相同，如果有异常，就返回这个原文本。</param>
        /// <param name="htmlBody">返回由这个参数加工的文本。</param>
        /// <param name="engine">脚本引擎。</param>
        /// <param name="scope">脚本范围（配置）。</param>
        /// <returns>返回由自定义 Python 脚本加工过后的文本（如果出现异常，就返回未加工的原文本）。</returns>
        internal static string CompileWithCustomPythonScripts(string srcFilePath, string directoryLevelMark,
                                                              string contentText, string htmlBody,
                                                              ScriptEngine engine, ScriptScope scope)
        {
            try
            {
                var list = new List<UIElement>();
                foreach (var item in Globals.MainWindow.lbxPythonScripts.Items)
                {
                    var ue = item as UIElement;
                    if (ue != null) list.Add(ue);
                }

                foreach (var item in Globals.MainWindow.lbxWorkspacePythonScripts.Items)
                {
                    var ue = item as UIElement;
                    if (ue != null) list.Add(ue);
                }

                foreach (var item in list)
                {
                    var psli = item as PythonScriptListItem;
                    if (psli == null || psli.CallMode != PythonScriptCallMode.BeforeCompileToHtml) continue;

                    var sourceCode = engine.CreateScriptSourceFromFile(psli.FullPath);
                    // 运行脚本
                    scope.SetVariable("inputText", htmlBody);
                    scope.SetVariable("outputText", htmlBody);
                    scope.SetVariable("documentText", htmlBody);
                    scope.SetVariable("ignoreTextChanging", false);
                    scope.SetVariable("pathToWorkspace", directoryLevelMark);
                    scope.SetVariable("pathOfWorkspace", Globals.PathOfWorkspace);
                    var fi = new FileInfo(srcFilePath);
                    scope.SetVariable("psArgs", new PythonScriptArgs()
                    {
                        MdFileInfo = fi,
                    });

                    L.GetKeyboardState(out bool isAlt, out bool isShift, out bool isCtrl);
                    scope.SetVariable("isCtrl", isCtrl);
                    scope.SetVariable("isShift", isShift);
                    scope.SetVariable("isAlt", isAlt);

                    // 重设搜索路径，默认的不管用
                    var pathList = engine.GetSearchPaths();
                    pathList.Add(Globals.PathOfPythonScripts);
                    pathList.Add(Globals.PathOfWorkspacePythonScripts);
                    pathList.Add(Globals.InstalledPath);
                    engine.SetSearchPaths(pathList);

                    scope.ImportModule("sys");
                    scope.ImportModule("clr");

                    sourceCode.Execute<string>(scope);

                    var ignoreTextChange = scope.GetVariable<bool>("ignoreTextChanging");
                    if (ignoreTextChange == false)
                    {
                        var text = scope.GetVariable<string>("outputText");
                        htmlBody = Utils.Text.FormatLineSplitter(text);
                    }
                }
            }
            catch (Exception ex)
            {
                ExceptionOperations eo = engine.GetService<ExceptionOperations>();
                string error = eo.FormatException(ex);
                LMessageBox.Show(error, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                htmlBody = contentText;
            }

            return htmlBody;
        }

        /// <summary>
        /// 自动转换三个反引号标记的代码段落。注意：三个反引号开头的行不仅用来标记代码片段，也用来标记写成原型的选择题。
        /// 所以在调用此方法之前，会先调用选择题原型转换方法。
        /// </summary>
        /// <param name="htmlBody"></param>
        /// <returns></returns>
        public static string CompileSlashQuotersCodeBlocks(string htmlBody)
        {
            if (string.IsNullOrEmpty(htmlBody)) return "";

            var lines = htmlBody.Split(new string[] { "\r\n", }, StringSplitOptions.None);

            var isOpen = false;
            var result = new StringBuilder();
            foreach (var line in lines)
            {
                if (line.StartsWith("```"))
                {
                    if (isOpen)
                    {
                        //result.Append("    " + line + "\r\n");
                        isOpen = false;
                        continue;
                    }
                    else
                    {
                        var language = line.Substring(3).Trim(new char[] { ' ', '　', '\t', '`', });
                        result.Append($"； > (@class:{language} code)" + "\r\n");
                        isOpen = true;
                        continue;
                    }
                }
                else
                {
                    if (isOpen)
                    {
                        result.Append("    " + line + "\r\n");
                    }
                    else
                    {
                        result.Append(line + "\r\n");
                    }
                }
            }
            return result.ToString();
        }

        /// <summary>
        /// 一定要记得每次开始编译前重置此值.
        /// </summary>
        private static int codeSampleIndex = 1;

        /// <summary>
        /// 抢先处理代码块，因为 MarkdownSharp 在处理两个 li 元素之间的代码块时会出问题。
        /// </summary>
        /// <param name="htmlBody"></param>
        /// <returns></returns>
        public static string CompileCodeBlocks(string htmlBody)
        {
            var lines = htmlBody.Split(new string[] { "\r\n", }, StringSplitOptions.None);

            var sb = new StringBuilder();
            bool codeBlockStart = false;
            foreach (var line in lines)
            {
                if (IsCodeBlockLine(line))
                {
                    bool isSampleMarkLine = false;
                    if (codeBlockStart == false)
                    {
                        if (line.Trim(new char[] { ' ', '　', '\t', '\n', '\r', }) == "{^}")
                        {
                            isSampleMarkLine = true;
                            sb.Append("\r\n<p class='lme-code' style='margin-bottom:0px;'><span class='lme-codemark'>CODE SAMPLE " + codeSampleIndex++ + "</span></p>\r\n<pre style='margin-top:0px;'><code>");
                        }
                        else
                        {
                            sb.Append("\r\n<pre><code>");
                        }
                        codeBlockStart = true;
                    }

                    if (isSampleMarkLine == false)
                    {
                        sb.Append(FormatCodeBlockLine(line).Replace("&", "&amp;").Replace("\"", "&quot;").Replace(" > ", "&gt;").Replace("<", "&lt;"));
                        sb.Append("\r\n");
                    }
                    continue;
                }
                else
                {
                    if (codeBlockStart)
                    {
                        sb.Append("</code></pre>\r\n");
                        codeBlockStart = false;
                    }
                    sb.Append(line);
                    sb.Append("\r\n");
                    continue;
                }
            }
            return sb.ToString();
        }

        private static string FormatCodeBlockLine(string line)
        {
            if (string.IsNullOrEmpty(line)) return string.Empty;

            if (line.StartsWith("\t")) return line.Substring(1);

            var tmp = line.Replace("　", " ");
            if (tmp.StartsWith("    "))
            {
                return line.Substring(4);
            }

            return line;
        }

        /// <summary>
        /// 解决 C2A0 编码冲突。UTF-8空格向GBK转码时会出错。
        /// 此方法改造自：https://www.cnblogs.com/lixiuke/p/9996504.html
        /// </summary>
        /// <param name="targetStr"></param>
        /// <returns></returns>
        public static string ChangeUTF8Space(string targetStr)
        {
            try
            {
                string currentStr = string.Empty;
                byte[] utf8Space = new byte[] { 0xc2, 0xa0 };
                string tempSpace = Encoding.GetEncoding("UTF-8").GetString(utf8Space);
                currentStr = targetStr.Replace(tempSpace, " ");
                return currentStr;
            }
            catch { return targetStr; }
        }

        /// <summary>
        /// 此方法用于编译 Markdown 文本中用户自定义的锚。
        /// 注意 方块区域标头文本中的 简写版的 锚 不在此方法中处理。
        /// </summary>
        /// <param name="htmlBody"></param>
        /// <returns></returns>
        private static string CompileAnchors(string htmlBody)
        {
            var sbNormalAnchor = new StringBuilder();
            var lines = htmlBody.Split(new string[] { "\r\n", }, StringSplitOptions.None);

            foreach (var line in lines)
            {
                if (IsCodeBlockLine(line) || IsSquareStart(line) || IsMultiText(line) || IsRegionStart(line))   //代码块中的锚不计算
                {
                    sbNormalAnchor.Append(line);
                    sbNormalAnchor.Append("\r\n");
                    continue;
                }

                var pieces = Utils.Text.SplitToPiecesByRegex("\\[.*\\][\\(（]@[a-zA-Z][a-zA-Z0-9\\-_]*?([ 　\t]*?\\^{0,1}(([\\\"].*?[\\\"])|(.*?)))?[\\)）]", line);
                var sb2 = new StringBuilder();

                foreach (var piece in pieces)
                {
                    if (piece.IsMartchText == false)
                    {
                        sb2.Append(piece.SourceText);
                        continue;
                    }

                    string anchor = FormatUrl(piece.SourceText, out string title, out string id,
                        out string alt, out bool isAnchor, out bool isSupper);
                    if (isAnchor == false)
                    {
                        sb2.Append(piece.SourceText);
                        continue;
                    }

                    sb2.Append($"<span id=\"{id}\" alt=\"{alt}\">{title}</span>{(isSupper ? " " + alt : "")}");
                }

                sbNormalAnchor.Append(sb2);
                sbNormalAnchor.Append("\r\n");
            }

            // 下面处理简化版的锚
            var sbEasyAnchor = new StringBuilder();
            var lines2 = sbNormalAnchor.ToString().Split(new string[] { "\r\n", }, StringSplitOptions.None);

            var trimChars = new char[] { ' ', '　', '\t', '(', '（', ')', '）', '@', };

            foreach (var line2 in lines2)
            {
                if (line2.StartsWith("[") || IsRegionStart(line2) || IsCodeBlockLine(line2) || IsMultiText(line2))        // 方块区域或自定义折叠区标头文本中的 简化锚 应由其自己处理。
                {
                    sbEasyAnchor.Append(line2);
                    sbEasyAnchor.Append("\r\n");
                    continue;
                }

                var pieces2 = Utils.Text.SplitToPiecesByRegex(@"[\(（][ 　\t]*?[@][ 　\t]*?[a-zA-Z][a-zA-Z0-9\-_]*?[ 　\t]*?[\)）]", line2);
                var sb4 = new StringBuilder();

                foreach (var piece2 in pieces2)
                {
                    if (piece2.IsMartchText == false)
                    {
                        sb4.Append(piece2.SourceText);
                        continue;
                    }

                    var id = piece2.SourceText.Substring(1, piece2.Length - 2).Trim(trimChars);

                    sb4.Append($"<span id=\"{id}\"></span>");
                }

                sbEasyAnchor.Append(sb4);
                sbEasyAnchor.Append("\r\n");
            }

            return sbEasyAnchor.ToString();
        }

        private static bool IsMultiText(string line)
        {
            Regex regexMultyText = new Regex(@"^[ 　]{0,3}<[1234567890１２３４５６７８９０]{1,}([.。][1234567890１２３４５６７８９０]{1,}){0,}>");
            var matchMultyText = regexMultyText.Match(line);
            return matchMultyText.Success;
        }

        private static object ShortCutScript(string previewPagePath, string indexPath, string nextPagePath)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<script language=\"javascript\" type=\"text/javascript\">\n");
            sb.Append("function onDocKeydown(e) {\n");
            sb.Append("e = e || window.event;\n");
            var hasPre = false;
            var hasIndex = false;
            var hasNext = false;
            if (string.IsNullOrWhiteSpace(previewPagePath) == false)
            {
                sb.Append("if(e.keyCode==37){　　　//快捷键 left\n");
                sb.Append($"window.location.href=\'{previewPagePath}\';\n");
                sb.Append("return;\n}\n");
                hasPre = true;
            }

            if (string.IsNullOrWhiteSpace(indexPath) == false)
            {
                sb.Append("if(e.ctrlKey && e.keyCode==73){　　　//快捷键 Ctrl+I \n");
                sb.Append($"window.location.href=\'{indexPath}\';\n");
                sb.Append("return;\n}\n");
                hasIndex = true;
            }

            if (string.IsNullOrWhiteSpace(nextPagePath) == false)
            {
                sb.Append("if(e.keyCode==39){　　　//快捷键 right\n");
                sb.Append($"window.location.href=\'{nextPagePath}\';\n");
                sb.Append("return;\n}\n");
                hasNext = true;
            }

            sb.Append("}\n");
            sb.Append("document.onkeydown=onDocKeydown;\n");
            sb.Append("</script>\n");

            if (hasPre || hasIndex || hasNext) return sb.ToString();

            return "";
        }

        private static string CompileTreeTableToList(string htmlBody)
        {
            if (string.IsNullOrWhiteSpace(htmlBody)) return "";

            var lines = htmlBody.Split(new string[] { "\r\n" }, StringSplitOptions.None);
            if (lines.Length <= 0) return "";

            List<TreeLine> treeLines = new List<TreeLine>();
            foreach (var t in lines)
            {
                treeLines.Add(new TreeLine() { Text = t, ShouldIgnore = false, });
            }

            List<TreeSegmentInfo> segs = new List<TreeSegmentInfo>();

            TreeSegmentInfo preSeg = null;
            for (int i = 0; i < lines.Length; i++)
            {
                var line = lines[i];
                if (IsTreeListTextLine(line))
                {
                    if (preSeg == null)
                    {
                        preSeg = new TreeSegmentInfo();
                        preSeg.StartLineIndex = i;
                        segs.Add(preSeg);
                    }
                }
                else
                {
                    if (preSeg != null)
                    {
                        preSeg.EndLineIndex = i - 1;
                        preSeg = null;
                    }
                }
            }

            if (segs.Count <= 0) return htmlBody;

            var treeTables = new List<TreeTextTable>();

            for (int i = 0; i < segs.Count; i++)
            {
                var seg = segs[i];
                if (seg.EndLineIndex < 0 || seg.StartLineIndex < 0 || seg.StartLineIndex >= lines.Length || seg.EndLineIndex < seg.StartLineIndex) continue;

                var treeTable = new TreeTextTable();

                var fstLine = lines[seg.StartLineIndex];
                TreeTextTableItem fstItem = null;
                string header;
                string tail;
                int level;
                if (MarkdownEditorBase.IsTreeListLine(fstLine, out header, out tail, out level))
                {
                    fstItem = new TreeTextTableItem() { Text = fstLine, TablePrefixMark = header, TailText = tail, Level = level, };
                    var regListHeaderMark = new Regex(@"^[ 　\t]{0,}\[[ 　\t]{0,}[.^]{0,1}[ 　\t]{0,}\][ 　\t]{0,}");
                    var m = regListHeaderMark.Match(tail);
                    if (m.Success)
                    {
                        if (m.Value.Contains('^'))
                        {
                            fstItem.ListHeaderMark = "[^]";  // 表示有序列表
                        }
                        else fstItem.ListHeaderMark = "[.]";  // 表示无序列表  [ ] 或 [] 也表示无序列表

                        fstItem.TailText = tail.Substring(m.Length);
                        treeTable.Items.Add(fstItem);
                    }
                    else continue;
                }
                else continue;

                var previewItem = fstItem;

                for (int j = seg.StartLineIndex + 1; j <= seg.EndLineIndex; j++)
                {
                    var treeLine = lines[j];
                    string header2;
                    string tail2;
                    int level2;
                    if (MarkdownEditorBase.IsTreeListLine(treeLine, out header2, out tail2, out level2))
                    {
                        var newItem = new TreeTextTableItem() { Text = treeLine, TablePrefixMark = header2, TailText = tail2, Level = level2, };
                        if (previewItem != null)
                        {
                            if (previewItem.Level == newItem.Level)
                            {
                                // 同级，添加到父级中去
                                if (previewItem.ParentItem != null)
                                {
                                    newItem.ParentItem = previewItem.ParentItem;
                                    previewItem.ParentItem.Items.Add(newItem);
                                    previewItem = newItem;
                                }
                                else
                                {
                                    treeTable.Items.Add(newItem);
                                    previewItem = newItem;
                                }
                            }
                            else if (previewItem.Level < newItem.Level)
                            {
                                // 新节点级别更深，作为上一个节点的子级
                                // 不允许直接出现深两级或更多的情况，故不考虑。
                                newItem.ParentItem = previewItem;
                                previewItem.Items.Add(newItem);
                                previewItem = newItem;
                            }
                            else if (previewItem.Level > newItem.Level)
                            {
                                // 新节点级别更浅，要寻找上一个有效父节点并添加
                                // 要考虑浅好几层的情况，要用循环找到最近的一个可用的父级
                                var pi = previewItem.ParentItem;
                                while (pi != null)
                                {
                                    if (pi.Level == newItem.Level - 1) break;
                                    else pi = pi.ParentItem;
                                }

                                if (pi == null)
                                {
                                    treeTable.Items.Add(newItem);
                                    previewItem = newItem;
                                }
                                else
                                {
                                    newItem.ParentItem = pi;
                                    pi.Items.Add(newItem);
                                    previewItem = newItem;
                                }
                            }
                        }
                    }
                    treeLines[j].ShouldIgnore = true;
                }

                treeLines[seg.StartLineIndex].Text = treeTable.HtmlText;
            }

            var sb2 = new StringBuilder();
            foreach (var tr in treeLines)
            {
                sb2.Append(tr.ShouldIgnore ? "" : (tr.Text + "\r\n"));
            }
            return sb2.ToString();
        }

        /// <summary>
        /// 仅用于将树型文字表编译为 html 列表。
        /// </summary>
        internal class TreeLine
        {
            internal string Text { get; set; }
            internal bool ShouldIgnore { get; set; }
        }

        /// <summary>
        /// 仅用于将树型文字表编译为 html 列表。
        /// </summary>
        internal enum ListType { Unknow, OrderedList, UnorderedList }

        /// <summary>
        /// 仅用于将树型文字表编译为 html 列表。
        /// </summary>
        internal class TreeTextTable
        {
            internal List<TreeTextTableItem> Items { get; } = new List<TreeTextTableItem>();

            internal ListType ListType
            {
                get
                {
                    if (Items.Count <= 0) return ListType.Unknow;

                    var fstItem = Items[0];
                    if (string.IsNullOrWhiteSpace(fstItem.ListHeaderMark) == false &&
                        fstItem.ListHeaderMark.Contains('^')) return ListType.OrderedList;

                    return ListType.UnorderedList;
                }
            }

            internal string HtmlText
            {
                get
                {
                    var type = this.ListType;
                    switch (type)
                    {
                        case ListType.UnorderedList:
                            {
                                var sb = new StringBuilder();
                                sb.Append("<ul class='f_tree_ulo'>\r\n");
                                foreach (var item in Items)
                                {
                                    sb.Append(item.BuildHtml(type));
                                }
                                sb.Append("</ul>\r\n");
                                return sb.ToString();
                            }
                        case ListType.OrderedList:
                            {
                                var sb = new StringBuilder();
                                sb.Append("<ol class='f_tree_olo'>\r\n");
                                foreach (var item in Items)
                                {
                                    sb.Append(item.BuildHtml(type));
                                }
                                sb.Append("</ol>\r\n");
                                return sb.ToString();
                            }
                        default: return "";
                    }
                }
            }
        }

        /// <summary>
        /// 仅用于将树型文字表编译为 html 列表。
        /// </summary>
        internal class TreeTextTableItem
        {
            internal int Level { get; set; }
            internal string Text { get; set; } = "";
            /// <summary>
            /// 一个树型文字表，只有第一行头部的  [^] [ ]  [.] 才可以指定应编译成有序或无序列表，其它情况下这种标记会被忽视并原文输出。
            /// </summary>
            internal string ListHeaderMark { get; set; } = "";
            internal string TailText { get; set; } = "";
            internal string TablePrefixMark { get; set; } = "";
            internal List<TreeTextTableItem> Items { get; } = new List<TreeTextTableItem>();
            internal TreeTextTableItem ParentItem { get; set; } = null;

            internal string BuildHtml(ListType listType)
            {
                switch (listType)
                {
                    case ListType.OrderedList:
                        {
                            var sb = new StringBuilder();
                            sb.Append("<li>");
                            sb.Append(TailText);
                            if (Items.Count > 0)
                            {
                                sb.Append("\r\n<ol class='f_tree_oli'>\r\n");
                                foreach (var subItem in Items)
                                {
                                    sb.Append(subItem.BuildHtml(listType));
                                }
                                sb.Append("</ol>\r\n");
                            }
                            sb.Append("</li>\r\n");
                            return sb.ToString();
                        }
                    case ListType.UnorderedList:
                        {
                            var sb = new StringBuilder();
                            sb.Append("<li>");
                            sb.Append(TailText);
                            if (Items.Count > 0)
                            {
                                sb.Append("\r\n<ul class='f_tree_uli'>\r\n");
                                foreach (var subItem in Items)
                                {
                                    sb.Append(subItem.BuildHtml(listType));
                                }
                                sb.Append("</ul>\r\n");
                            }
                            sb.Append("</li>\r\n");
                            return sb.ToString();
                        }
                    default: return "";
                }
            }
        }

        /// <summary>
        /// 仅用于将树型文字表编译为 html 列表。
        /// </summary>
        internal class TreeSegmentInfo
        {
            internal int StartLineIndex { get; set; } = -1;
            internal int EndLineIndex { get; set; } = -1;
        }

        /// <summary>
        /// 格式化锚的自动编号。
        /// </summary>
        /// <param name="sourceText">源文本。</param>
        /// <returns>返回替换了自动编号的文本</returns>
        public static string FormatAndCompileAutoNumberOfAnchors(string sourceText)
        {
            var lines = sourceText.Split(new string[] { "\r\n" }, StringSplitOptions.None);
            var sb = new StringBuilder();

            var reg = new Regex(@"\[[ 　\t]{0,}?\^[ 　\t]{0,}?\](?=(\]\([@]))");

            int number = 1;

            foreach (var line in lines)
            {
                if (CustomMarkdownSupport.IsCodeBlockLine(line))
                {
                    sb.Append(line);
                    sb.Append("\r\n");
                    continue;
                }

                var lineText = line;
                var matches = reg.Matches(line);
                if (matches.Count > 0)
                {
                    number += (matches.Count - 1);
                    for (int i = matches.Count - 1; i >= 0; i--)
                    {
                        var match = matches[i];
                        lineText = lineText.Substring(0, match.Index) + "[" + number.ToString() + "]" + lineText.Substring(match.Index + match.Length);
                        lineText = CompileUrls(lineText);
                        number--;
                    }
                    number += matches.Count;
                    number++;
                }

                sb.Append(lineText);
                sb.Append("\r\n");
            }

            return sb.ToString();
        }

        /// <summary>
        /// 是否块级代码行。
        /// </summary>
        public static bool IsCodeBlockLine(string line)
        {
            if (string.IsNullOrEmpty(line)) return false;
            return (line.StartsWith("    ") || line.StartsWith("\t"));
        }

        /// <summary>
        /// 是否块级代码行。
        /// 注意，此方法会自动进行 Html 转义。
        /// </summary>
        public static bool IsCodeBlockLine(string line, out string codeContent)
        {
            if (string.IsNullOrEmpty(line))
            {
                codeContent = null; return false;
            }

            if (line.StartsWith("\t"))
            {
                codeContent = FormatCodeBlockLine(line.Substring(1)).Replace("&", "&amp;").Replace("\"", "&quot;").Replace(" > ", "&gt;").Replace("<", "&lt;");
                return true;
            }

            var reg = new Regex(@"^[ 　]{4,4}");
            var match = reg.Match(line);
            if (match.Success)
            {
                codeContent = line.Substring(match.Length);
                codeContent = FormatCodeBlockLine(codeContent).Replace("&", "&amp;").Replace("\"", "&quot;").Replace(" > ", "&gt;").Replace("<", "&lt;");
                return true;
            }
            else
            {
                codeContent = null;
                return false;
            }
        }

        /// <summary>
        /// 替换 &lt;:xxx:&gt; 这样锚引用标签为引用字符串。
        /// </summary>
        private static string FormatSpecialAnchorReferences(string htmlBody, string srcFilePath, bool forEpub = false)
        {
            // <:xxx:> 锚引用标签处理
            if (string.IsNullOrWhiteSpace(htmlBody)) return htmlBody;
            var reg = new Regex(@"<[:：].*?[：:]>");
            htmlBody = htmlBody.Replace("\r\n", "\n");
            htmlBody = htmlBody.Replace("\r", "\n");
            string[] lines = htmlBody.Split(new char[] { '\n', }, StringSplitOptions.None);

            StringBuilder sb = new StringBuilder();
            foreach (var line in lines)
            {
                if (line.StartsWith("\t") || line.StartsWith("    "))
                {
                    sb.Append(line); sb.Append("\r\n"); continue;
                }

                var matches = reg.Matches(line);
                if (matches.Count <= 0)
                {
                    sb.Append(line); sb.Append("\r\n"); continue;
                }

                var trimChars = new char[] { ' ', '　', '\t' };
                if (AnchorsManager.HasAnchor == false)
                {
                    AnchorsManager.Load();
                    if (AnchorsManager.HasAnchor == false)
                    {
                        AnchorsManager.RebuildAnchorsFromWorkspaceFiles();
                    }
                }

                string lineText = line;

                for (int i = matches.Count - 1; i >= 0; i--)
                {
                    var m = matches[i];
                    var name = m.Value.Substring(2, m.Value.Length - 4).Trim(trimChars);
                    lineText = lineText.Substring(0, m.Index) + AnchorsManager.BuilderLinks(name, srcFilePath, forEpub) + lineText.Substring(m.Index + m.Length);
                }

                sb.Append(lineText);
                sb.Append("\r\n");
            }

            return sb.ToString();
        }

        /// <summary>
        /// 注意：如果编译的目的是导出 Epub 文档，要注意 mdDocPath 应传入它原本所在位置的路径，不然计算引用的图像链接会出错。
        /// </summary>
        /// <param name="htmlDocumentText"></param>
        /// <param name="mdDocPath">此参数用来计算图像链接的相对路径，需要编译的 Markdown 文件的原始路径。</param>
        /// <param name="forEpub"></param>
        /// <returns></returns>
        public static string FormatHtml(string htmlDocumentText, string originalMarkdownFilePath = null, bool forEpub = false, List<MarkdownFile> mdFilesList = null, bool onlyFormatIamgeStyleFromTitleText = false)
        {
            NSoup.Nodes.Document doc = NSoup.NSoupClient.Parse(htmlDocumentText);

            #region 给无序列表项添加多种 class
            var liElements = doc.GetElementsByTag("li");
            var trimBlankChars = new char[] { ' ', '　', '\t', '\r', '\n', };
            foreach (var liElement in liElements)
            {
                var innerHtml = liElement.Html();
                var lowerText = innerHtml.ToLower();
                if (lowerText.StartsWith("<p>") && lowerText.EndsWith("</p>"))
                {
                    innerHtml = innerHtml.Substring(3, innerHtml.Length - 7);
                }

                innerHtml = innerHtml.Trim(trimBlankChars);

                if (innerHtml.StartsWith("<!--clean li-->"))
                {
                    var className = liElement.ClassName();
                    if (string.IsNullOrWhiteSpace(className) == false)
                    {
                        liElement.AddClass("clean");
                    }
                    else
                    {
                        liElement.Attr("class", "clean");
                    }
                    liElement.Html("<p class=\"li_clean\">" + innerHtml.Substring("<!--clean li-->".Length) + "</p>");
                }
                else if (innerHtml.StartsWith("<!--default li-->"))
                {
                    var className = liElement.ClassName();
                    if (string.IsNullOrWhiteSpace(className) == false)
                    {
                        liElement.AddClass("default");
                    }
                    else
                    {
                        liElement.Attr("class", "default");
                    }
                    liElement.Html("<p class=\"li_default\">" + innerHtml.Substring("<!--default li-->".Length) + "</p>");
                }
                else if (innerHtml.StartsWith("<!--custom li-->"))
                {
                    var className = liElement.ClassName();
                    if (string.IsNullOrWhiteSpace(className) == false)
                    {
                        liElement.AddClass("custom");
                    }
                    else
                    {
                        liElement.Attr("class", "custom");
                    }
                    liElement.Html("<p class=\"li_custom\">" + innerHtml.Substring("<!--custom li-->".Length) + "</p>");
                }
            }

            #endregion

            var imgelements = doc.GetElementsByTag("img");
            var regStyle = new Regex(@"(?<=(style\=[\\""\'])).+(?=[\\""\'])");
            foreach (var imgelement in imgelements)
            {
                // 让图像链接支持设置Style
                bool hasSelfStyle = false;
                if (imgelement.HasAttr("title"))
                {
                    var titleAttrVal = imgelement.Attr("title").ToLower().Trim();
                    var matchStyle = regStyle.Match(titleAttrVal);
                    if (matchStyle.Success)
                    {
                        imgelement.RemoveAttr("title");
                        imgelement.Attr("style", matchStyle.Value);
                        hasSelfStyle = true;
                    }
                }

                if (imgelement.HasAttr("alt"))  // 试题中的 Style 会出现在 alt 而不是 title 中。
                {
                    var altAttrVal = imgelement.Attr("alt").ToLower().Trim();
                    var matchStyle = regStyle.Match(altAttrVal);
                    if (matchStyle.Success)
                    {
                        imgelement.RemoveAttr("alt");
                        imgelement.Attr("style", matchStyle.Value);
                        hasSelfStyle = true;
                    }
                }

                if (imgelement.HasAttr("src"))
                {
                    var srcAttr = imgelement.Attr("src");
                    if (srcAttr.EndsWith("$"))
                    {
                        imgelement.Attr("src", srcAttr.TrimEnd(new char[] { '$' }));
                        if (hasSelfStyle == false && imgelement.HasAttr("style") == false &&
                            string.IsNullOrWhiteSpace(Globals.MainWindow.ImageDefaultStyle) == false)
                        {
                            imgelement.Attr("style", Globals.MainWindow.ImageDefaultStyle);
                        }
                    }

                    var srcAttrVal = imgelement.Attr("src").ToLower().Trim();
                    if (srcAttrVal.Contains("--light--"))
                    {
                        imgelement.Attr("class", "lme_hide_when_dark");
                    }
                    else if (srcAttrVal.Contains("--dark--"))
                    {
                        imgelement.Attr("class", "lme_hide_when_light");
                    }
                }

                if (Globals.MainWindow.CorrectImagePath)
                {
                    if (imgelement.HasAttr("src"))
                    {
                        var imgSrcAttrVal = imgelement.Attr("src".Trim());
                        var realPath = Utils.ImageLinkTool.GetRealPathFromRelativePath(imgSrcAttrVal, originalMarkdownFilePath);
                        var realFi = new FileInfo(realPath);
                        if (realFi.Exists)
                        {
                            var x = Directory.GetFiles(realFi.Directory.FullName, realFi.Name);
                            if (x.Length > 0)
                            {
                                var diskRealPath = x[0];
                                if (realPath.Length == diskRealPath.Length)
                                {
                                    var trimedPath = imgSrcAttrVal.TrimStart(new char[] { '.', '/' });
                                    if (realPath.ToLower().Replace("\\", "/").EndsWith(trimedPath.ToLower()))
                                    {
                                        imgelement.Attr("src", imgSrcAttrVal.Substring(0, imgSrcAttrVal.Length - trimedPath.Length) + diskRealPath.Substring(diskRealPath.Length - trimedPath.Length).Replace("\\", "/"));
                                    }
                                }
                            }
                        }
                    }
                }
            }

            var layoutTds = doc.GetElementsByClass("line_layout_td");
            foreach (var layoutTd in layoutTds)
            {
                var imgs = layoutTd.GetElementsByTag("img");
                foreach (var img in imgs)
                {
                    img.Attr("style", "width:100%;");  // 只有为 100% 时，td 的宽度才是有效的，否则图像的宽度不由单元格宽度控制。
                }
            }

            // 解决多标点标记（多问号、句号、叹号）带合并行标记时的材料出处自动右对齐的问题。
            var paragraphs = doc.GetElementsByTag("p");
            foreach (var paragraph in paragraphs)
            {
                var paraClassName = paragraph.ClassName();
                var paraText = paragraph.Text();
                if (string.IsNullOrWhiteSpace(paraClassName)) continue;

                if (paraClassName.StartsWith("para_header_tail_"))
                {
                    if (paraText.StartsWith("——"))
                    {
                        paragraph.AddClass("ex_m_cc");
                        continue;
                    }

                    if (paraText.StartsWith("→→"))
                    {
                        paragraph.AddClass("ex_m_cc");
                        paragraph.Text("——" + paraText.Substring(2));
                        continue;
                    }
                }
            }

            // 解决一个单独的 <p></p> 中只有一个 <img /> 元素的问题。
            foreach (var imgelement in imgelements)
            {
                var parentElement = imgelement.Parent;
                if (parentElement == null) continue;

                if (parentElement.TagName().ToLower() == "p" && parentElement.ChildNodes.Count == 1)
                {
                    parentElement.ReplaceWith(imgelement);
                }
            }

            if (forEpub && string.IsNullOrEmpty(originalMarkdownFilePath) == false)
            {
                var pathOfEpubUserImages = Globals.PathOfWorkspace + "_EpubTemp~\\OEBPS\\Images~\\_user\\";

                if (Directory.Exists(pathOfEpubUserImages) == false)
                {
                    Directory.CreateDirectory(pathOfEpubUserImages);
                }

                var elements = doc.GetElementsByTag("img");
                foreach (var element in elements)
                {
                    if (element.HasAttr("is_quick_image"))
                        continue;

                    var relativeImagePath = element.Attr("src");
                    var dotIndex = relativeImagePath.LastIndexOf(".");
                    string suffix = "";
                    if (dotIndex >= 0)
                    {
                        suffix = relativeImagePath.Substring(dotIndex);
                    }
                    var imageRealPath = MarkdownEditor.ConvertRelativeImageLinkPathToFull(relativeImagePath, originalMarkdownFilePath);
                    var workspaceImageFolderPath = Globals.PathofWorkspaceImages;
                    if (relativeImagePath.StartsWith("./Images~/") == false)
                    {
                        var newFileName = "Img_" + (Directory.GetFiles(pathOfEpubUserImages).Length + 1) + suffix;  // 必须重命名，否则难以解决不同目录下复制到这里的图像文件同名的冲突问题。
                        if (File.Exists(imageRealPath))
                        {
                            File.Copy(imageRealPath, pathOfEpubUserImages + newFileName);
                        }
                        element.Attr("src", "../Images~/_user/" + newFileName);
                    }
                    else
                    {
                        element.Attr("src", "../Images~/" + relativeImagePath.Substring("./Images~/".Length).Replace("\\", "/"));
                    }
                }
            }

            var linkElements = doc.GetElementsByTag("a");
            if (forEpub && mdFilesList != null && mdFilesList.Count > 0)
            {
                foreach (var element in linkElements)
                {
                    // 这里要小心，html 文件都被移动到同一个目录下并重命名为 ...00n.xhtml 了
                    element.Attr("href", ConvertEpubRelativePath(element.Attr("href"), originalMarkdownFilePath, mdFilesList));
                }
            }

            if (forEpub == false && string.IsNullOrWhiteSpace(originalMarkdownFilePath) == false)
            {
                // 非 epub 脚注的引用链接自动弹出消息框显示 title 文本
                var originalHtmlFilePath = originalMarkdownFilePath.Substring(0, originalMarkdownFilePath.Length - 3) + ".html";

                foreach (var aelement in linkElements)
                {
                    var issupAttr = aelement.Attr("issup");
                    if (issupAttr == null || issupAttr.ToLower() != "true") continue;

                    var title = aelement.Attr("title");
                    if (title.StartsWith("\"") && title.EndsWith("\""))
                    {
                        aelement.Attr("title", title.Substring(1, title.Length - 2));
                    }

                    var hrefVal = aelement.Attr("href");
                    var hrefFilePath = hrefVal;
                    var lstIndex = hrefFilePath.LastIndexOf("#");
                    if (lstIndex < 0 || lstIndex >= hrefFilePath.Length - 1) continue;    //最后一个字符是 # 说明没有 ID
                    var hrefId = hrefFilePath.Substring(lstIndex + 1);

                    hrefFilePath = hrefFilePath.Substring(0, lstIndex).ToLower();

                    var fi = new FileInfo(originalHtmlFilePath);

                    if (hrefVal.StartsWith("#"))
                    {
                        aelement.Attr("href", "javascript:void(0);");
                        aelement.Attr("link-to", hrefId.Trim(new char[] { '#', ' ', '　', '\t', }));
                        aelement.Attr("onclick", "showPopupText(this);");
                        aelement.Attr("popup-height", "25%");
                    }
                    else if (fi.Name.ToLower() == hrefFilePath)
                    {
                        if (hrefVal.StartsWith(".") == false && hrefVal.Contains("#"))
                        {
                            aelement.Attr("href", "javascript:void(0);");
                            aelement.Attr("link-to", hrefId);
                            aelement.Attr("onclick", "showPopupText(this);");
                            aelement.Attr("popup-height", "25%");
                        }
                    }
                }
            }
            else
            {
                // epub 下不自动弹出消息框显示 title（脚注文本），这是因为有些阅读器自身就带这个功能。
                foreach (var element in linkElements)
                {
                    var title = element.Attr("title");
                    if (title.StartsWith("\"") && title.EndsWith("\""))
                    {
                        element.Attr("title", title.Substring(1, 2));
                    }
                }
            }

            // 注意：这个不能太早，不然一些功能会失效。
            if (onlyFormatIamgeStyleFromTitleText) return doc.OuterHtml();       // 只更新 Image 的内联样式表

            htmlDocumentText = doc.OuterHtml();
            var text = "\n <!-- saved from url=(0016)http://localhost -->\n";
            var index = htmlDocumentText.IndexOf(text);
            if (index >= 0)
            {
                htmlDocumentText = htmlDocumentText.Substring(0, index) +
                    "\r\n<!-- saved from url=(0016)http://localhost -->\r\n"  // 必须加上这个处理，否则在IE中不起作用，仍然会弹出警告信息条。
                    + htmlDocumentText.Substring(index + text.Length);
            }
            return htmlDocumentText.Replace("&middot;", "·");   // 间隔号这个实体在导出电子书时用 sigil 打开会报错。
        }

        private static string ConvertEpubRelativePath(string hrefValue, string originalMarkdownFilePath, List<MarkdownFile> mdFilesList)
        {
            if (string.IsNullOrWhiteSpace(hrefValue)) return hrefValue;
            if (string.IsNullOrWhiteSpace(originalMarkdownFilePath)) return hrefValue;
            if (mdFilesList == null || mdFilesList.Count <= 0) return hrefValue;

            var lastIndexOfSharp = hrefValue.LastIndexOf('#');
            var anchor = "";
            var hrefValueHeader = "";
            if (lastIndexOfSharp >= 0)
            {
                anchor = hrefValue.Substring(lastIndexOfSharp + 1);
                hrefValueHeader = hrefValue.Substring(0, lastIndexOfSharp);
            }
            else
            {
                hrefValueHeader = hrefValue;
            }

            foreach (var mdFile in mdFilesList)
            {
                var relativePath = MarkdownEditorBase.GetRelativePath(originalMarkdownFilePath, mdFile.FullPath, true).ToLower();
                var originalHtmlFilePath = originalMarkdownFilePath.Substring(0, originalMarkdownFilePath.Length - 3) + ".html";
                if (hrefValueHeader.ToLower().EndsWith(relativePath))
                {
                    //var lastIndexOfSlash = relativePath.LastIndexOf('/');
                    //if (lastIndexOfSlash >= 0)  // 有可能引用原属同一目录下的文件，此时不应出现 / 字符
                    //{
                    var tmpPath = mdFile.EpubTempPath.Replace("\\", "/");
                    var lastIndexOfSlash2 = tmpPath.LastIndexOf('/');
                    if (lastIndexOfSlash2 >= 0)
                    {
                        var newShortName = mdFile.EpubTempPath.Substring(lastIndexOfSlash2 + 1);
                        if (newShortName.ToLower().EndsWith(".html"))
                        {
                            newShortName = newShortName.Substring(0, newShortName.Length - 5) + ".xhtml";
                        }
                        string newHrefValue;
                        var fi = new FileInfo(originalHtmlFilePath);
                        if (fi.Name.ToLower() == relativePath)
                        {
                            newHrefValue = (anchor.Length > 0 ? "#" : "") + anchor; // 如果引用的就是当前文件中的某个锚
                        }
                        else
                        {
                            newHrefValue = newShortName + (anchor.Length > 0 ? "#" : "") + anchor;  // 引用其它文件中的锚
                        }

                        return newHrefValue;
                    }
                    //}
                }
            }

            return hrefValue;
        }

        /// <summary>
        /// 转换标准 Markdown 片段。
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        public static string CompileStandardMarkdownSegment(string src)
        {
            src = src.Replace("\r\n", "\n");
            var md = new MarkdownSharp.Markdown();
            return md.Transform(src);
        }

        /// <summary>
        /// 自动尝试将所有以双引号括起来的标题转换为链接。前提工作区管理器中要存在与此标题一致的条目。
        /// 注意：这种自动生成的链接是依赖标题而不是依赖文件名的，所以可能失效。
        /// </summary>
        /// <param name="fullPath">当前正在编译的 markdown 文档完整路径。</param>
        /// <param name="htmlBody">正在编译的文本。</param>
        /// <returns></returns>
        private static string TryConvertTitlesToLink(string fullPath, string htmlBody)
        {
            try
            {
                StringBuilder sb = new StringBuilder(htmlBody);
                Regex reg = new Regex(@"(?<=《).*?(?=》)");
                var matches = reg.Matches(htmlBody);
                var regImageLinks = new Regex(@"(!\[.*?\]\(.*?\))|(<[a-zA-Z][a-zA-Z0-9]*( .*?)?>.*</[a-zA-Z][a-zA-Z0-9]*?>)");
                var matchesImageLinks = regImageLinks.Matches(htmlBody);

                if (matches.Count > 0)
                {
                    for (int i = matches.Count - 1; i >= 0; i--)
                    {
                        var m = matches[i];
                        // 如果在图像链接中，则不应自动创建指向文档的链接！
                        if (IsInImageLinkOrHtmlPiece(m.Index, matchesImageLinks)) continue;

                        string shortName = m.Value;
                        var item = Globals.MainWindow.FindWorkspaceTreeViewItemByTitle(m.Value);
                        if (item == null) continue;

                        var resPath = item.FullPath;
                        if (item.IsDirectoryExists)
                        {
                            if (item.IsMetaDirectoryPath)
                            {
                                resPath = Globals.MainWindow.GetMetaFilePathOfDirectory(item.FullPath);
                            }
                            else continue;
                        }

                        sb = sb.Replace(m.Value, Globals.MainWindow.BuildLinkText(resPath, fullPath, ref shortName), m.Index, m.Length);
                    }
                }

                return sb.ToString();
            }
            catch
            {
                return htmlBody;
            }
        }

        /// <summary>
        /// 判断某个位置是否在当前文档的某个图像链接或已编译的 Html 片段内部。
        /// </summary>
        /// <param name="index"></param>
        /// <param name="matchesImageLinks"></param>
        /// <returns></returns>
        private static bool IsInImageLinkOrHtmlPiece(int index, MatchCollection matchesImageLinks)
        {
            foreach (Match match in matchesImageLinks)
            {
                if (index > match.Index && index < match.Index + match.Length)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// 取出用户自己在文档内部写的 <Script>...</Script> 块。
        /// </summary>
        private static string GetPageScripts(string contentText, out string ScriptTexts)
        {
            if (string.IsNullOrWhiteSpace(contentText)) { ScriptTexts = ""; return contentText; }

            //string startRegionScript = @"^[<][Ss][Cc][Rr][Ii][Pp][Tt]([ ]{1,}.*)?[>]";
            //string endRegionScript = @"^[<][/][Ss][Cc][Rr][Ii][Pp][Tt][>]";

            var reg = new Regex(@"(^([\.]{2}[ 　\t]{0,})?[<][Ss][Cc][Rr][Ii][Pp][Tt]([ ]{1,}.*)?[>])[\s\S]*?(([\.]{2}[ 　\t]{0,})?[<][/][Ss][Cc][Rr][Ii][Pp][Tt][>])", RegexOptions.Multiline);
            var matches = reg.Matches(contentText);
            if (matches.Count <= 0) { ScriptTexts = ""; return contentText; }

            StringBuilder sb = new StringBuilder();
            var splitter = new char[] { '\r', '\n', };
            foreach (Match match in matches)
            {
                if (match.Length <= 0) continue;
                var lines = match.Value.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
                if (lines.Length <= 0) continue;
                StringBuilder sbIn = new StringBuilder();
                foreach (var line in lines)
                {
                    if (IsCommentLine(line)) continue;
                    //{
                    //    sbIn.Append($"<!--{line}-->\n");
                    //}
                    //为什么不用这个办法？
                    //这是因为低版本的浏览器不支持内嵌代码，所以会将内部的 Script 标签及其内容显示在页面上。
                    //为避免此现象出现，常常会写成下面这样：
                    //<Script>
                    //<!--
                    //    ...
                    //-->
                    //</Script>
                    //这样做的目的是：如果浏览器不支持这样的内部样式表，就将其中的内容视为Html注释。
                    //而如果将冒号开头的注释行也改写成 Html 样式的注释，就会产生冲突。
                    if (line.StartsWith(".."))
                    {
                        sbIn.Append($"{line.Substring(2).TrimStart()}\n");
                    }
                    else
                    {
                        sbIn.Append($"{line}\n");
                    }
                }
                sb.Append(sbIn.ToString() + "\n");
            }

            ScriptTexts = sb.ToString();

            StringBuilder result = new StringBuilder();
            result.Append(contentText.Substring(0, matches[0].Index));
            result.Append("\r\n");

            for (int i = 0; i < matches.Count - 1; i++)
            {
                var thisMatch = matches[i];
                var nextMatch = matches[i + 1];
                var thisEnd = thisMatch.Index + thisMatch.Length;
                result.Append(contentText.Substring(thisEnd, nextMatch.Index - thisEnd));
                result.Append("\r\n");
            }

            var lastMatch = matches[matches.Count - 1];
            result.Append(contentText.Substring(lastMatch.Index + lastMatch.Length));
            result.Append("\r\n");

            return result.ToString();
        }

        /// <summary>
        /// 取出用户自己在文档内部写的 <Style>...</Style> 块。
        /// </summary>
        private static string GetPageStyles(string contentText, out string styleTexts)
        {
            if (string.IsNullOrWhiteSpace(contentText)) { styleTexts = ""; return contentText; }

            //string startRegionStyle = @"^[<][Ss][Tt][Yy][Ll][Ee]([ ]{1,}.*)?[>]";
            //string endRegionStyle = @"^[<][/][Ss][Tt][Yy][Ll][Ee][>]";

            var reg = new Regex(@"(^[<][Ss][Tt][Yy][Ll][Ee]([ ]{1,}.*)?[>])[\s\S]*?([<][/][Ss][Tt][Yy][Ll][Ee][>])", RegexOptions.Multiline);
            var matches = reg.Matches(contentText);
            if (matches.Count <= 0) { styleTexts = ""; return contentText; }

            StringBuilder sb = new StringBuilder();
            var splitter = new char[] { '\r', '\n', };
            foreach (Match match in matches)
            {
                if (match.Length <= 0) continue;
                var lines = match.Value.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
                if (lines.Length <= 0) continue;
                StringBuilder sbIn = new StringBuilder();
                foreach (var line in lines)
                {
                    if (IsCommentLine(line)) continue;
                    //{
                    //    sbIn.Append($"<!--{line}-->\n");
                    //}
                    //为什么不用这个办法？
                    //这是因为低版本的浏览器不支持这样的内部样式表，所以会将内部的 Style 标签及其内容显示在页面上。
                    //为避免此现象出现，常常会写成下面这样：
                    //<Style>
                    //<!--
                    //    body { background: red;}
                    //-->
                    //</Style>
                    //这样做的目的是：如果浏览器不支持这样的内部样式表，就将其中的内容视为Html注释。
                    //而如果将冒号开头的注释行也改写成 Html 样式的注释，就会产生冲突。

                    sbIn.Append($"{line}\n");
                }
                sb.Append(sbIn.ToString() + "\n");
            }

            styleTexts = sb.ToString();

            StringBuilder result = new StringBuilder();
            result.Append(contentText.Substring(0, matches[0].Index));
            result.Append("\r\n");

            for (int i = 0; i < matches.Count - 1; i++)
            {
                var thisMatch = matches[i];
                var nextMatch = matches[i + 1];
                var thisEnd = thisMatch.Index + thisMatch.Length;
                result.Append(contentText.Substring(thisEnd, nextMatch.Index - thisEnd));
                result.Append("\r\n");
            }

            var lastMatch = matches[matches.Count - 1];
            result.Append(contentText.Substring(lastMatch.Index + lastMatch.Length));
            result.Append("\r\n");

            return result.ToString();
        }

        /// <summary>
        /// 如果段落中只有一个 Img 标签，则将此 Imag 标签从段落中独立出来。这样可以实现图像本身的对齐效果。
        /// </summary>
        /// <param name="match"></param>
        /// <returns></returns>
        public static string FormatParagraphsTag(Match match)
        {
            if (match.Length < 7) return match.Value;
            return match.Value.Substring(3, match.Length - 7);
        }

        private static string BuildIndexOrHomePageLink(string srcFilePath, out string indexOrHomePagePath)
        {
            if (srcFilePath.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()))
            {
                var tail = srcFilePath.Substring(Globals.PathOfWorkspace.Length);
                if (tail.StartsWith("\\") || tail.StartsWith("/"))
                {
                    tail = tail.Substring(1);
                }

                var spans = tail.Split(new char[] { '\\', '/' }, StringSplitOptions.None);

                StringBuilder sb = new StringBuilder();
                for (int i = 1; i < spans.Length; i++)//注意i从1开始，不然目录会多一层。
                {
                    sb.Append("../");
                }

                if (Globals.MainWindow.AutoCreateIndexHtmlFile)
                {
                    indexOrHomePagePath = $"{sb.ToString()}_index.html";
                    return $"<a href=\"{indexOrHomePagePath}\" alt=\"Ctrl+I\">回目录</a>";
                }
                else
                {
                    var fstHomePageItem = Globals.MainWindow.FindFirstHomePageItem();
                    if (fstHomePageItem != null)
                    {
                        var shortHtmlPath = "";
                        switch (fstHomePageItem.ItemType)
                        {
                            case WorkspaceTreeViewItem.Type.File:
                                {
                                    if (fstHomePageItem.FullPath.ToLower() == srcFilePath.ToLower())
                                        return indexOrHomePagePath = "";

                                    var hFileInfo = new FileInfo(fstHomePageItem.HtmlFullPath);
                                    shortHtmlPath = hFileInfo.FullName.Substring(Globals.PathOfWorkspace.Length);
                                    break;
                                }
                            case WorkspaceTreeViewItem.Type.Folder:
                            case WorkspaceTreeViewItem.Type.MetaFile:
                                {
                                    if (fstHomePageItem.MetaFilePath.ToLower() == srcFilePath.ToLower())
                                        return indexOrHomePagePath = "";

                                    var hFileInfo = new FileInfo(fstHomePageItem.MetaHtmlFilePath);
                                    shortHtmlPath = hFileInfo.FullName.Substring(Globals.PathOfWorkspace.Length);
                                    break;
                                }
                        }
                        indexOrHomePagePath = $"{sb.ToString()}{shortHtmlPath}";
                        return $"<a href=\"{indexOrHomePagePath}\" alt=\"Ctrl+I\">回首页</a>";
                    }
                    else
                    {
                        var rootItem = Globals.MainWindow.RootItem;
                        if (rootItem == null) return indexOrHomePagePath = "";
                        else
                        {
                            var shortHtmlPath = "";
                            switch (rootItem.ItemType)
                            {
                                case WorkspaceTreeViewItem.Type.File:
                                    {
                                        // 正常情况下，RootItem 肯定不可能是个 File
                                        if (rootItem.FullPath.ToLower() == srcFilePath.ToLower())
                                            return indexOrHomePagePath = "";

                                        var hFileInfo = new FileInfo(rootItem.HtmlFullPath);
                                        shortHtmlPath = hFileInfo.FullName.Substring(Globals.PathOfWorkspace.Length);
                                        break;
                                    }
                                case WorkspaceTreeViewItem.Type.Folder:
                                case WorkspaceTreeViewItem.Type.MetaFile:
                                    {
                                        if (rootItem.MetaFilePath.ToLower() == srcFilePath.ToLower())
                                            return indexOrHomePagePath = "";

                                        var hFileInfo = new FileInfo(rootItem.MetaHtmlFilePath);
                                        shortHtmlPath = hFileInfo.FullName.Substring(Globals.PathOfWorkspace.Length);
                                        break;
                                    }
                            }
                            indexOrHomePagePath = $"{sb.ToString()}{shortHtmlPath}";
                            return $"<a href=\"{indexOrHomePagePath}\" alt=\"Ctrl+I\">回首页</a>";
                        }
                    }
                }
            }

            return indexOrHomePagePath = "";
        }

        /// <summary>
        /// 判断指定文件是否被加密。
        /// </summary>
        /// <param name="srcFilePath">Markdown 文件的路径。</param>
        /// <param name="password">用以传出加密文件的密码。</param>
        /// <param name="passwordTip">用以传出加密文件的密码提示信息。</param>
        /// <returns></returns>
        public static bool IsFileEncrypted(string srcFilePath, out string password, out string passwordTip)
        {
            if (File.Exists(srcFilePath) == false)
            {
                password = null;
                passwordTip = null;
                return false;
            }

            using (var sr = new StreamReader(srcFilePath))
            {
                var fstLine = sr.ReadLine();

                if (string.IsNullOrWhiteSpace(fstLine))
                {
                    passwordTip = password = "";
                    return false;
                }

                var decryptedFstLine = SetPasswordPanel.TextDecrypt(
                    fstLine.Replace("[[<r>]]", "\r").Replace("[[<n>]]", "\n").ToCharArray(), "DyBj#PpBb");
                var indexOfPassword = decryptedFstLine.IndexOf("Password:");
                var indexOfQuestion = decryptedFstLine.IndexOf("Question:");

                password = "";
                passwordTip = "";
                if (indexOfQuestion >= 0 && indexOfPassword >= 10)
                {
                    password = decryptedFstLine.Substring(indexOfPassword + 9);
                    passwordTip = decryptedFstLine.Substring(9, indexOfPassword - 10);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 判断此磁盘文件第一行是否带“废弃”标记文本。
        /// </summary>
        /// <param name="srcFilePath"></param>
        /// <returns></returns>
        public static bool IsFileAborted(string srcFilePath)
        {
            bool result = false;
            if (File.Exists(srcFilePath) == false) return false;

            using (var sr = new StreamReader(srcFilePath))
            {
                var fstLine = sr.ReadLine();
                if (string.IsNullOrWhiteSpace(fstLine)) return false;

                result = IsAbortedTaskLine(fstLine);
            }
            return result;
        }

        /// <summary>
        /// 取当前路径相对于当前工作区路径的层级差。
        /// </summary>
        /// <param name="fullFilePath">文件路径。</param>
        public static int GetSubLayerOfDirectory(string fullFilePath)
        {
            if (string.IsNullOrEmpty(fullFilePath)) return -1;

            if (fullFilePath.ToLower().StartsWith(Globals.PathOfWorkspace.ToLower()) == false) return -1;

            var tailTextOfPath = fullFilePath.Substring(Globals.PathOfWorkspace.Length);

            int i = 0;
            foreach (char c in tailTextOfPath)
            {
                if (c == '\\')
                {
                    i++;
                }
            }
            return i;
        }

        /// <summary>
        /// 根据文件路径与当前工作区路径之间的层级差来生成相对引用字符串前缀。
        /// </summary>
        /// <param name="fullFilePath">文件路径。</param>
        public static string GetDirectoryLevelMark(string fullFilePath)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i <= GetSubLayerOfDirectory(fullFilePath) - 1; i++)
            {
                sb.Append("../");
            }
            return sb.ToString();
        }

        /// <summary>
        /// 取系统默认浏览器路径。
        /// </summary>
        public static string GetDefaultBrowserPath()
        {
            string key = @"HTTP\shell\open\command";
            using (RegistryKey registrykey = Registry.ClassesRoot.OpenSubKey(key, false))
            {
                return ((string)registrykey.GetValue(null, null)).Split('"')[1];
            }
        }

        /// <summary>
        /// 调用系统默认浏览器打开本地 Html 网页。
        /// </summary>
        /// <param name="url"></param>
        public static void OpenLocalHtmlFileWithDefaultWebBrowser(string url)
        {
            if (url.Contains(' '))
            {
                url = UrlEncode(url);
            }
            System.Diagnostics.Process.Start(GetDefaultBrowserPath(), string.Format("file:///{0}", url));
        }

        /// <summary>
        /// 在内置预览区预览指定本地网页。
        /// 警告：不能传入 URL。
        /// </summary>
        /// <param name="htmlFilePath"></param>
        public static void PreviewHtml(string htmlFilePath)
        {
            if (File.Exists(htmlFilePath) == false)
            {
                LMessageBox.Show("没有传入有效的本地 Html 文件路径！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            var destUri = new Uri(htmlFilePath);//不能加这个前缀

            if (Globals.MainWindow.previewFrame.Source == null ||
                        Globals.MainWindow.previewFrame.Source.AbsolutePath != destUri.AbsolutePath)
            {
                Globals.MainWindow.previewFrame.Source = destUri;
            }
            else Globals.MainWindow.previewFrame.Refresh();

            Globals.MainWindow.tcRightToolBar.SelectedItem = Globals.MainWindow.tiHtmlPreview;

            if (Globals.MainWindow.tcRightToolBar.ActualWidth < 100)
            {
                Globals.MainWindow.cdMainEditArea.Width =
                    Globals.MainWindow.cdRightToolsArea.Width = new GridLength(3, GridUnitType.Star);
                //Globals.MainWindow.cdLeftToolsArea.Width = new GridLength(460, GridUnitType.Pixel);
            }
        }
    }

    /// <summary>
    /// 此类用于在编译处理 <h1></h1>...<h6></h6>，以便使其这些标题支持折叠、自动编号等功能。
    /// </summary>
    public class HtmlHeaderInfo
    {
        /// <summary>
        /// 此标题所需要的 JavaScript。
        /// </summary>
        public string JavaScriptText { get; set; } = "";

        /// <summary>
        /// 此标题之前所有标题对应 Div 的结尾字符串。因为前面可能有几层，所以可能像这样：“</div></div></div>”。
        /// </summary>
        public string PreviewHeaderPanelCloseDivsMark { get; set; } = "";

        /// <summary>
        /// 转换后的文本。
        /// </summary>
        public string NewText { get; set; } = "";

        /// <summary>
        /// 此标题是否已关闭。
        /// </summary>
        public bool IsClosed { get; set; } = false;

        /// <summary>
        /// 标题文本。
        /// </summary>
        public string Header { get; set; } = "";

        /// <summary>
        /// Html文本。
        /// </summary>
        public string Html { get; set; } = "";

        /// <summary>
        /// 标题层级。
        /// </summary>
        public int Level { get; set; } = 0;

        /// <summary>
        /// 从 Html 在提取出来的正则匹配信息。
        /// </summary>
        public Match Match { get; set; } = null;
    }
}
